Bisher gab es nur PayPal. Jetzt sind pro Mandant drei unabhaengig aktivierbare Zahlungswege konfigurierbar (mandant-einstellungen.php): - Barzahlung mit frei waehlbarem Ansprechpartner. - PayPal wie bisher, mit Zahlungslink. - Ueberweisung mit Kontoinhaber und IBAN (Format wird geprueft, Leerzeichen werden normalisiert). Das Mitglieder-Dashboard (index.php) zeigt unter "Bezahlen" alle aktivierten Methoden mit dem jeweils offenen Betrag; die IBAN wird zur besseren Lesbarkeit in Viererbloecken angezeigt. Neue tenant_settings-Spalten via Migration 0021 (cash_enabled, cash_contact, bank_transfer_enabled, bank_account_holder, bank_iban).
432 lines
15 KiB
PHP
432 lines
15 KiB
PHP
<?php
|
|
|
|
require_once __DIR__ . "/app/bootstrap.php";
|
|
|
|
// Marketingdomain (z. B. kaffeeliste.de) zeigt die Landingpage statt des
|
|
// Dashboards; nur der Host aus APP_HOST bekommt die eigentliche App. Ohne
|
|
// gesetztes APP_HOST (lokale Entwicklung) verhaelt sich index.php wie
|
|
// bisher immer als Dashboard.
|
|
$appHost = app_primary_host();
|
|
$requestHost = strtolower(preg_replace('/:\d+$/', '', (string)($_SERVER['HTTP_HOST'] ?? '')));
|
|
if ($appHost !== null && $requestHost !== strtolower($appHost)) {
|
|
require __DIR__ . '/landing.php';
|
|
exit;
|
|
}
|
|
|
|
include "functions.php";
|
|
require_once __DIR__ . "/app/ledger.php";
|
|
app_require_csrf();
|
|
|
|
$pdo = app_db_pdo();
|
|
$saasUser = saas_current_user($pdo);
|
|
$tenantId = 0;
|
|
$hasAccess = false;
|
|
$emailNorm = strtolower(trim($mailadress));
|
|
|
|
if ($saasUser !== null) {
|
|
$tenantId = (int)$saasUser['tenant_id'];
|
|
$emailNorm = strtolower(trim((string)$saasUser['email_norm']));
|
|
$hasAccess = true;
|
|
}
|
|
|
|
if (!$hasAccess && checkKaffeelisteAccess($conn, $mailadress)) {
|
|
$tenant = ledger_fetch_default_tenant($pdo);
|
|
if ($tenant !== null) {
|
|
$tenantId = (int)$tenant['id'];
|
|
$hasAccess = true;
|
|
}
|
|
}
|
|
|
|
$participant = null;
|
|
if ($hasAccess) {
|
|
// Eingeloggte SaaS-Nutzer werden primaer ueber die feste user_id-
|
|
// Verknuepfung ihres Teilnehmers gefunden (bei Registrierung bzw.
|
|
// Zugangsvergabe gesetzt) - robuster als der E-Mail-Abgleich, der bei
|
|
// abweichender oder geleerter Teilnehmer-E-Mail ins Leere liefe. Der
|
|
// Legacy-/Dev-Pfad ohne SaaS-User faellt weiterhin auf die E-Mail zurueck.
|
|
$saasUserId = $saasUser !== null ? (int)$saasUser['user_id'] : 0;
|
|
if ($saasUserId > 0) {
|
|
$summaries = ledger_fetch_participant_summaries($pdo, $tenantId, [
|
|
'user_ids' => [$saasUserId],
|
|
]);
|
|
$participant = $summaries[0] ?? null;
|
|
}
|
|
if ($participant === null && $emailNorm !== '') {
|
|
$summaries = ledger_fetch_participant_summaries($pdo, $tenantId, [
|
|
'email_norms' => [$emailNorm],
|
|
]);
|
|
$participant = $summaries[0] ?? null;
|
|
}
|
|
}
|
|
|
|
$settings = $hasAccess ? saas_fetch_tenant_settings($pdo, $tenantId) : null;
|
|
$entryMessage = null;
|
|
$entryError = null;
|
|
|
|
// Ergebnis einer vorangegangenen Aktion (Post-Redirect-Get): nach dem Buchen
|
|
// bzw. Stornieren wird auf index.php zurueckgeleitet, damit ein Browser-Refresh
|
|
// die Aktion nicht ein zweites Mal ausloest.
|
|
if (isset($_SESSION['flash_dashboard'])) {
|
|
$flash = $_SESSION['flash_dashboard'];
|
|
unset($_SESSION['flash_dashboard']);
|
|
if (($flash['type'] ?? '') === 'success') {
|
|
$entryMessage = (string)$flash['text'];
|
|
} else {
|
|
$entryError = (string)$flash['text'];
|
|
}
|
|
}
|
|
|
|
function dashboard_money(int $cents): string
|
|
{
|
|
return saas_format_money_cents($cents) . ' €';
|
|
}
|
|
|
|
function dashboard_date(string $value): string
|
|
{
|
|
try {
|
|
return (new DateTimeImmutable($value))->format('d.m.Y');
|
|
} catch (Throwable $e) {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
function dashboard_current_name(?array $saasUser, ?array $participant, mixed $conn, string $mailadress): string
|
|
{
|
|
$name = trim((string)($saasUser['display_name'] ?? ''));
|
|
if ($name !== '') {
|
|
return $name;
|
|
}
|
|
|
|
$name = trim((string)($participant['display_name'] ?? ''));
|
|
if ($name !== '') {
|
|
return $name;
|
|
}
|
|
|
|
return trim((string)getUserName($conn, $mailadress));
|
|
}
|
|
|
|
function dashboard_paypal_action(string $template, string $amount): string
|
|
{
|
|
return trim($template) . $amount;
|
|
}
|
|
|
|
/**
|
|
* @return array{ok: bool, message?: string, error?: string}
|
|
*/
|
|
function dashboard_record_self_entry(PDO $pdo, array $participant, array $settings, mixed $marksValue, ?int $actorUserId): array
|
|
{
|
|
if ((int)$settings['self_entry_enabled'] !== 1) {
|
|
return ['ok' => false, 'error' => 'Die eigene Stricherfassung ist aktuell deaktiviert.'];
|
|
}
|
|
|
|
$marks = filter_var($marksValue, FILTER_VALIDATE_INT);
|
|
if ($marks !== 1 && $marks !== 2) {
|
|
return ['ok' => false, 'error' => 'Bitte einen oder zwei Striche auswählen.'];
|
|
}
|
|
|
|
$unitPriceCents = (int)$settings['mark_price_cents'];
|
|
if ($unitPriceCents <= 0) {
|
|
return ['ok' => false, 'error' => 'Der Preis pro Strich ist nicht gültig konfiguriert.'];
|
|
}
|
|
|
|
$legacyMitarbeiterId = $participant['legacy_mitarbeiter_id'] !== null
|
|
? (int)$participant['legacy_mitarbeiter_id']
|
|
: 0;
|
|
|
|
try {
|
|
if ($legacyMitarbeiterId > 0) {
|
|
// Migrierter Default-Mandant: weiterhin in die Legacy-Tabelle
|
|
// schreiben, damit die noch-legacy Sammelseiten (stricheintragen.php)
|
|
// den Eintrag ebenfalls sehen; der Ledger-Eintrag entsteht per
|
|
// Spiegelung.
|
|
$bookedAt = date('Y-m-d H:i:s');
|
|
$cost = (($marks * $unitPriceCents) / 100);
|
|
$unitPrice = ($unitPriceCents / 100);
|
|
|
|
$pdo->beginTransaction();
|
|
$stmt = $pdo->prepare(
|
|
'INSERT INTO kl_Kaffeeverbrauch
|
|
(MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum, Eintragsart)
|
|
VALUES (?, ?, ?, ?, ?, 2)'
|
|
);
|
|
$stmt->execute([$legacyMitarbeiterId, $marks, $cost, $unitPrice, $bookedAt]);
|
|
$legacyConsumptionId = (int)$pdo->lastInsertId();
|
|
if ($legacyConsumptionId <= 0) {
|
|
throw new RuntimeException('Legacy-Strich konnte nicht angelegt werden.');
|
|
}
|
|
|
|
ledger_mirror_legacy_consumption($pdo, (int)$participant['tenant_id'], $legacyConsumptionId);
|
|
$pdo->commit();
|
|
} else {
|
|
// SaaS-native Mandanten (alle ausser dem migrierten Default-
|
|
// Mandanten) haben keine Legacy-Schattentabelle - direkt ins
|
|
// Ledger buchen.
|
|
ledger_record_consumption(
|
|
$pdo,
|
|
(int)$participant['tenant_id'],
|
|
(int)$participant['participant_id'],
|
|
$marks,
|
|
$unitPriceCents,
|
|
'self_entry',
|
|
$actorUserId
|
|
);
|
|
}
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
|
|
return ['ok' => false, 'error' => 'Die Stricheintragung konnte nicht gespeichert werden.'];
|
|
}
|
|
|
|
return ['ok' => true, 'message' => 'Stricheintragung wurde erfolgreich eingetragen.'];
|
|
}
|
|
|
|
function dashboard_render_payments(array $entries): void
|
|
{
|
|
echo "<h4>Letzte Einzahlungen</h4>";
|
|
echo "<table><tr><th style='width:120px'>Datum</th><th>Einzahlung</th></tr>";
|
|
if ($entries === []) {
|
|
echo "<tr><td colspan='2'>Keine Einzahlungen vorhanden.</td></tr>";
|
|
}
|
|
foreach ($entries as $entry) {
|
|
echo "<tr>";
|
|
echo "<td>" . saas_html(dashboard_date((string)$entry['booked_at'])) . "</td>";
|
|
echo "<td>" . saas_html(dashboard_money((int)$entry['amount_cents'])) . "</td>";
|
|
echo "</tr>";
|
|
}
|
|
echo "</table>";
|
|
}
|
|
|
|
function dashboard_render_consumption(array $entries): void
|
|
{
|
|
echo "<h4>Letzte Striche</h4>";
|
|
echo "<table><tr><th style='width:120px'>Datum</th><th>Striche</th><th>Kosten</th></tr>";
|
|
if ($entries === []) {
|
|
echo "<tr><td colspan='3'>Keine Striche vorhanden.</td></tr>";
|
|
}
|
|
foreach ($entries as $entry) {
|
|
echo "<tr>";
|
|
echo "<td>" . saas_html(dashboard_date((string)$entry['booked_at'])) . "</td>";
|
|
echo "<td>" . number_format((int)($entry['marks_count'] ?? 0), 0, ',', '.') . "</td>";
|
|
echo "<td>" . saas_html(dashboard_money(abs((int)$entry['amount_cents']))) . "</td>";
|
|
echo "</tr>";
|
|
}
|
|
echo "</table>";
|
|
}
|
|
|
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|
$flash = ['type' => 'error', 'text' => 'Die Aktion konnte nicht ausgeführt werden.'];
|
|
|
|
if ($hasAccess && $participant !== null && $settings !== null) {
|
|
$aktion = (string)($_POST['aktion'] ?? 'strich_eintragen');
|
|
|
|
if ($aktion === 'strich_stornieren') {
|
|
$result = ledger_void_own_self_entry($pdo, $tenantId, (int)$participant['participant_id']);
|
|
$flash = $result['ok']
|
|
? ['type' => 'success', 'text' => 'Dein zuletzt selbst eingetragener Strich wurde storniert.']
|
|
: ['type' => 'error', 'text' => $result['error'] ?? 'Der Strich konnte nicht storniert werden.'];
|
|
} else {
|
|
$result = dashboard_record_self_entry($pdo, $participant, $settings, $_POST["anzahlStriche"] ?? null, $saasUser['user_id'] ?? null);
|
|
$flash = $result['ok']
|
|
? ['type' => 'success', 'text' => $result['message'] ?? 'Stricheintragung wurde erfolgreich eingetragen.']
|
|
: ['type' => 'error', 'text' => $result['error'] ?? 'Die Stricheintragung konnte nicht gespeichert werden.'];
|
|
}
|
|
}
|
|
|
|
// Post-Redirect-Get: Ergebnis in die Session legen und per GET erneut
|
|
// anzeigen, damit ein Refresh die Buchung/Stornierung nicht wiederholt.
|
|
$_SESSION['flash_dashboard'] = $flash;
|
|
header('Location: index.php');
|
|
exit;
|
|
}
|
|
|
|
$paymentEntries = $participant !== null
|
|
? ledger_fetch_recent_entries($pdo, $tenantId, [
|
|
'participant_id' => $participant['participant_id'],
|
|
'type' => 'payment',
|
|
'limit' => 100,
|
|
])
|
|
: [];
|
|
$consumptionEntries = $participant !== null
|
|
? ledger_fetch_recent_entries($pdo, $tenantId, [
|
|
'participant_id' => $participant['participant_id'],
|
|
'type' => 'consumption',
|
|
'limit' => 100,
|
|
])
|
|
: [];
|
|
|
|
include "header.php";
|
|
include "headerline.php";
|
|
include "nav.php";
|
|
?>
|
|
|
|
<section id="banner">
|
|
<div class="content">
|
|
<?php
|
|
if ($hasAccess && $participant !== null && $settings !== null) {
|
|
$balanceCents = (int)$participant['balance_cents'];
|
|
$balance = dashboard_money($balanceCents);
|
|
$currentName = dashboard_current_name($saasUser, $participant, $conn, $mailadress);
|
|
$selfEntryEnabled = (int)$settings['self_entry_enabled'] === 1;
|
|
$paypalEnabled = (int)$settings['paypal_enabled'] === 1
|
|
&& trim((string)$settings['paypal_url_template']) !== '';
|
|
$cashEnabled = (int)$settings['cash_enabled'] === 1;
|
|
$bankTransferEnabled = (int)$settings['bank_transfer_enabled'] === 1
|
|
&& trim((string)$settings['bank_iban']) !== '';
|
|
$anyPaymentEnabled = $paypalEnabled || $cashEnabled || $bankTransferEnabled;
|
|
?>
|
|
<h2>Kaffeeliste</h2>
|
|
<?php if ($currentName !== ''): ?>
|
|
Hallo <?php echo saas_html($currentName); ?>!<br><br>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($entryMessage !== null): ?>
|
|
<div class="hint-box success"><p><b><?php echo saas_html($entryMessage); ?></b><br>Du kannst den Eintrag weiter unten kontrollieren.</p></div><br>
|
|
<?php endif; ?>
|
|
<?php if ($entryError !== null): ?>
|
|
<div class="hint-box error"><p><b><?php echo saas_html($entryError); ?></b></p></div><br>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
if ($balanceCents > 0) {
|
|
echo "<p><b>Aktueller Stand: " . saas_html($balance) . " (Guthaben)</b></p>";
|
|
} elseif ($balanceCents < 0) {
|
|
echo "<p><b>Aktueller Stand: " . saas_html($balance) . " (Schulden)</b></p>";
|
|
} else {
|
|
echo "<p><b>Aktueller Stand: " . saas_html($balance) . "</b></p>";
|
|
}
|
|
?>
|
|
|
|
<h2>Jahresübersicht</h2>
|
|
Ausgabe im aktuellen Jahr: <?php echo saas_html(dashboard_money((int)$participant['year_costs_cents'])); ?><br>
|
|
Gesamtstriche im aktuellen Jahr: <?php echo number_format((int)$participant['year_marks'], 0, ',', '.'); ?><br>
|
|
<p>Gesamteinzahlung im aktuellen Jahr: <?php echo saas_html(dashboard_money((int)$participant['year_payments_cents'])); ?></p>
|
|
|
|
<?php
|
|
$aktuellesJahr = (int)date('Y');
|
|
$monatsdaten = ledger_fetch_monthly_consumption($pdo, $tenantId, (int)$participant['participant_id'], $aktuellesJahr);
|
|
$monatsnamen = [1 => 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
|
|
$aktuellerMonat = (int)date('n');
|
|
if ((int)$participant['year_marks'] > 0):
|
|
?>
|
|
<h2>Monatsübersicht <?php echo $aktuellesJahr; ?></h2>
|
|
<table>
|
|
<tr><th style="width:160px">Monat</th><th>Striche</th><th>Verbrauch</th></tr>
|
|
<?php
|
|
for ($m = 1; $m <= $aktuellerMonat; $m++):
|
|
$md = $monatsdaten[$m];
|
|
?>
|
|
<tr>
|
|
<td><?php echo saas_html($monatsnamen[$m]); ?></td>
|
|
<td><?php echo number_format($md['marks'], 0, ',', '.'); ?></td>
|
|
<td><?php echo saas_html(dashboard_money($md['cost_cents'])); ?></td>
|
|
</tr>
|
|
<?php endfor; ?>
|
|
</table>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($selfEntryEnabled): ?>
|
|
<h2>Eintrag in die Strichliste</h2>
|
|
<b>Hier kannst du einen oder zwei Striche für dich in der Kaffeeliste eintragen. <br>Dafür benötigst du keinen Eintrag auf der Liste durchführen.</b><br>
|
|
Aktueller Preis pro Strich: <?php echo saas_html(dashboard_money((int)$settings['mark_price_cents'])); ?><br><br>
|
|
<ul class="actions">
|
|
<li>
|
|
<form method="post" action="index.php">
|
|
<button type="submit">Einen Strich eintragen</button>
|
|
<input type="hidden" name="aktion" value="strich_eintragen">
|
|
<input type="hidden" name="anzahlStriche" value="1">
|
|
<?php echo app_csrf_field(); ?>
|
|
</form>
|
|
</li>
|
|
<li>
|
|
<form method="post" action="index.php">
|
|
<button type="submit">Zwei Striche eintragen</button>
|
|
<input type="hidden" name="aktion" value="strich_eintragen">
|
|
<input type="hidden" name="anzahlStriche" value="2">
|
|
<?php echo app_csrf_field(); ?>
|
|
</form>
|
|
</li>
|
|
<li>
|
|
<form method="post" action="index.php" onsubmit="return confirm('Deinen zuletzt selbst eingetragenen Strich stornieren?');">
|
|
<button type="submit" class="alt">Letzten Strich stornieren</button>
|
|
<input type="hidden" name="aktion" value="strich_stornieren">
|
|
<?php echo app_csrf_field(); ?>
|
|
</form>
|
|
</li>
|
|
</ul>
|
|
<small>Falsch getippt? Mit „Letzten Strich stornieren" nimmst du deinen jüngsten selbst eingetragenen Strich wieder zurück. Vom Kassenwart eingetragene Striche bleiben davon unberührt.</small>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($anyPaymentEnabled): ?>
|
|
<h2>Bezahlen</h2>
|
|
<?php if ($balanceCents < 0): ?>
|
|
<p>Dein offener Betrag: <b><?php echo saas_html(dashboard_money(abs($balanceCents))); ?></b></p>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($cashEnabled): ?>
|
|
<h4>Barzahlung</h4>
|
|
<?php if (trim((string)$settings['cash_contact']) !== ''): ?>
|
|
<p>Bitte bar bei <b><?php echo saas_html($settings['cash_contact']); ?></b> bezahlen.</p>
|
|
<?php else: ?>
|
|
<p>Barzahlung ist möglich.</p>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($bankTransferEnabled): ?>
|
|
<h4>Überweisung</h4>
|
|
<p>
|
|
<?php if (trim((string)$settings['bank_account_holder']) !== ''): ?>
|
|
Kontoinhaber: <b><?php echo saas_html($settings['bank_account_holder']); ?></b><br>
|
|
<?php endif; ?>
|
|
IBAN: <b><?php echo saas_html(trim(chunk_split((string)$settings['bank_iban'], 4, ' '))); ?></b>
|
|
<?php if ($balanceCents < 0): ?>
|
|
<br>Bitte überweise deinen offenen Betrag von <b><?php echo saas_html(dashboard_money(abs($balanceCents))); ?></b>.
|
|
<?php endif; ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($paypalEnabled): ?>
|
|
<h4>PayPal</h4>
|
|
<b>Bezahle immer über die Freunde-Funktion von PayPal. Ansonsten stellen wir 20% des Betrags als Gebühr in Rechnung.</b><br>
|
|
<br>
|
|
<?php
|
|
$paypalTemplate = trim((string)$settings['paypal_url_template']);
|
|
if ($balanceCents < 0) {
|
|
$debtCents = abs($balanceCents);
|
|
$debtAmount = dashboard_money($debtCents);
|
|
echo '<form action="' . saas_html(dashboard_paypal_action($paypalTemplate, saas_format_money_cents($debtCents))) . '" target="_blank"><button type="submit">' . saas_html($debtAmount) . ' bezahlen</button></form>';
|
|
}
|
|
?>
|
|
<ul class="actions">
|
|
<li><form action="<?php echo saas_html(dashboard_paypal_action($paypalTemplate, '5')); ?>" target="_blank"><button type="submit">5,00 € einzahlen</button></form></li>
|
|
<li><form action="<?php echo saas_html(dashboard_paypal_action($paypalTemplate, '10')); ?>" target="_blank"><button type="submit">10,00 € einzahlen</button></form></li>
|
|
<li><form action="<?php echo saas_html(dashboard_paypal_action($paypalTemplate, '15')); ?>" target="_blank"><button type="submit">15,00 € einzahlen</button></form></li>
|
|
</ul>
|
|
<?php endif; ?>
|
|
|
|
<br><br>
|
|
<?php
|
|
dashboard_render_payments($paymentEntries);
|
|
echo "<br>";
|
|
dashboard_render_consumption($consumptionEntries);
|
|
?>
|
|
<br><br>
|
|
|
|
<form action="namenanpassen.php" method="get">
|
|
<button type="submit">Namensanpassung</button>
|
|
</form>
|
|
<?php
|
|
} elseif ($hasAccess) {
|
|
echo "<h2>Kaffeeliste</h2>";
|
|
echo "<p>Für dein Konto wurde im aktuellen Mandanten kein Teilnehmer gefunden.</p>";
|
|
} else {
|
|
echo "<h2>Sie haben keinen Zugang zu dieser Webseite</h2>";
|
|
}
|
|
?>
|
|
</div>
|
|
</section>
|
|
|
|
<?php include "footer.php"; ?>
|