Haerte Verwaltungsflows und Zahlungsabgleich
This commit is contained in:
+5
-3
@@ -39,14 +39,16 @@ function imports_find_participant(PDO $pdo, int $tenantId, string $name): ?array
|
|||||||
FROM participants
|
FROM participants
|
||||||
WHERE tenant_id = ?
|
WHERE tenant_id = ?
|
||||||
AND (LOWER(paypal_name) = LOWER(?) OR LOWER(display_name) = LOWER(?))
|
AND (LOWER(paypal_name) = LOWER(?) OR LOWER(display_name) = LOWER(?))
|
||||||
LIMIT 1'
|
ORDER BY id
|
||||||
|
LIMIT 2'
|
||||||
);
|
);
|
||||||
$stmt->execute([$tenantId, $name, $name]);
|
$stmt->execute([$tenantId, $name, $name]);
|
||||||
$row = $stmt->fetch();
|
$rows = $stmt->fetchAll();
|
||||||
|
|
||||||
if ($row === false) {
|
if (count($rows) !== 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
$row = $rows[0];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'participant_id' => (int)$row['id'],
|
'participant_id' => (int)$row['id'],
|
||||||
|
|||||||
+34
-10
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
include "functions.php";
|
include "functions.php";
|
||||||
require_once __DIR__ . "/app/ledger.php";
|
require_once __DIR__ . "/app/ledger.php";
|
||||||
app_require_csrf();
|
app_require_csrf();
|
||||||
@@ -55,13 +57,23 @@ function einzahlung_fetch_participants(PDO $pdo, int $tenantId, string $action):
|
|||||||
$eingetragen = 0;
|
$eingetragen = 0;
|
||||||
$fehlgeschlagen = false;
|
$fehlgeschlagen = false;
|
||||||
$hatGespeichert = false;
|
$hatGespeichert = false;
|
||||||
|
$erfolgsmeldung = null;
|
||||||
$validierungsFehler = [];
|
$validierungsFehler = [];
|
||||||
$eingaben = [];
|
$eingaben = [];
|
||||||
|
|
||||||
|
if (isset($_SESSION['flash_einzahlung']) && is_array($_SESSION['flash_einzahlung'])) {
|
||||||
|
$flash = $_SESSION['flash_einzahlung'];
|
||||||
|
unset($_SESSION['flash_einzahlung']);
|
||||||
|
if (($flash['type'] ?? '') === 'success') {
|
||||||
|
$erfolgsmeldung = sprintf('%d Einträge erfolgreich hinzugefügt.', (int)($flash['count'] ?? 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Obergrenze je Einzahlungszeile. Faengt Groessenordnungs-Tippfehler ab
|
// Obergrenze je Einzahlungszeile. Faengt Groessenordnungs-Tippfehler ab
|
||||||
// (z. B. 500 statt 5,00); groessere Betraege lassen sich in mehreren
|
// (z. B. 500 statt 5,00); groessere Betraege lassen sich in mehreren
|
||||||
// Schritten eintragen.
|
// Schritten eintragen.
|
||||||
const EINZAHLUNG_MAX_BETRAG = 1000.00;
|
const EINZAHLUNG_MAX_BETRAG = 1000.00;
|
||||||
|
const EINZAHLUNG_MAX_BETRAG_CENTS = 100000;
|
||||||
|
|
||||||
// Verarbeitung des Formulars, wenn es gesendet wurde
|
// Verarbeitung des Formulars, wenn es gesendet wurde
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
||||||
@@ -89,6 +101,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
$rohBetrag = trim((string)$anzahlBetrag);
|
$rohBetrag = trim((string)$anzahlBetrag);
|
||||||
$bemerkung = trim((string)($_POST['bemerkung'][$participantId] ?? ''));
|
$bemerkung = trim((string)($_POST['bemerkung'][$participantId] ?? ''));
|
||||||
$eingaben[$participantId] = ['betrag' => $rohBetrag, 'bemerkung' => $bemerkung];
|
$eingaben[$participantId] = ['betrag' => $rohBetrag, 'bemerkung' => $bemerkung];
|
||||||
|
$name = $namen[$participantId] ?? ('Teilnehmer ' . $participantId);
|
||||||
|
|
||||||
if ($rohBetrag === '') {
|
if ($rohBetrag === '') {
|
||||||
// Leere Zeile: nur meckern, wenn trotzdem eine Bemerkung dransteht
|
// Leere Zeile: nur meckern, wenn trotzdem eine Bemerkung dransteht
|
||||||
@@ -102,16 +115,23 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$anzahlBetrag = floatval(str_replace(',', '.', $rohBetrag));
|
$betragCents = saas_parse_money_cents($rohBetrag);
|
||||||
if ($anzahlBetrag == 0.0) {
|
if ($betragCents === null) {
|
||||||
|
$validierungsFehler[] = sprintf(
|
||||||
|
'%s: Der Betrag "%s" ist kein gültiger Geldbetrag.',
|
||||||
|
$name,
|
||||||
|
$rohBetrag
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = $namen[$participantId] ?? ('Teilnehmer ' . $participantId);
|
if ($betragCents === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Abzuege muessen begruendet werden, sonst ist spaeter nicht mehr
|
// Abzuege muessen begruendet werden, sonst ist spaeter nicht mehr
|
||||||
// nachvollziehbar, warum jemandem Geld abgezogen wurde.
|
// nachvollziehbar, warum jemandem Geld abgezogen wurde.
|
||||||
if ($anzahlBetrag < 0 && $bemerkung === '') {
|
if ($betragCents < 0 && $bemerkung === '') {
|
||||||
$validierungsFehler[] = sprintf(
|
$validierungsFehler[] = sprintf(
|
||||||
'%s: Bei einem Abzug (negativer Betrag) ist eine Bemerkung Pflicht.',
|
'%s: Bei einem Abzug (negativer Betrag) ist eine Bemerkung Pflicht.',
|
||||||
$name
|
$name
|
||||||
@@ -120,18 +140,18 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Groessenordnungs-Tippfehler abfangen (500 statt 5,00).
|
// Groessenordnungs-Tippfehler abfangen (500 statt 5,00).
|
||||||
if (abs($anzahlBetrag) > EINZAHLUNG_MAX_BETRAG) {
|
if (abs($betragCents) > EINZAHLUNG_MAX_BETRAG_CENTS) {
|
||||||
$validierungsFehler[] = sprintf(
|
$validierungsFehler[] = sprintf(
|
||||||
'%s: %s € übersteigt die Plausibilitätsgrenze von %s €. Bitte prüfen oder in mehreren Schritten eintragen.',
|
'%s: %s € übersteigt die Plausibilitätsgrenze von %s €. Bitte prüfen oder in mehreren Schritten eintragen.',
|
||||||
$name,
|
$name,
|
||||||
number_format($anzahlBetrag, 2, ',', '.'),
|
saas_format_money_cents($betragCents),
|
||||||
number_format(EINZAHLUNG_MAX_BETRAG, 2, ',', '.')
|
number_format(EINZAHLUNG_MAX_BETRAG, 2, ',', '.')
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$zuBuchen[$participantId] = [
|
$zuBuchen[$participantId] = [
|
||||||
'betrag' => $anzahlBetrag,
|
'betrag_cents' => $betragCents,
|
||||||
'bemerkung' => $bemerkung !== '' ? $bemerkung : null,
|
'bemerkung' => $bemerkung !== '' ? $bemerkung : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -147,7 +167,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
$pdo,
|
$pdo,
|
||||||
$tenantId,
|
$tenantId,
|
||||||
$participantId,
|
$participantId,
|
||||||
(int)round($zeile['betrag'] * 100),
|
(int)$zeile['betrag_cents'],
|
||||||
'manual_bulk',
|
'manual_bulk',
|
||||||
$saasUser['user_id'] ?? null,
|
$saasUser['user_id'] ?? null,
|
||||||
$zeile['bemerkung']
|
$zeile['bemerkung']
|
||||||
@@ -157,6 +177,9 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
$eingaben = []; // Erfolgreich gebucht: Formular wieder leeren.
|
$eingaben = []; // Erfolgreich gebucht: Formular wieder leeren.
|
||||||
|
$_SESSION['flash_einzahlung'] = ['type' => 'success', 'count' => $eingetragen];
|
||||||
|
header('Location: einzahlung.php');
|
||||||
|
exit;
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if ($pdo->inTransaction()) {
|
if ($pdo->inTransaction()) {
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
@@ -188,10 +211,11 @@ $mitarbeiter = einzahlung_fetch_participants($pdo, $tenantId, $action);
|
|||||||
</div>
|
</div>
|
||||||
<?php elseif ($fehlgeschlagen): ?>
|
<?php elseif ($fehlgeschlagen): ?>
|
||||||
<div class="hint-box error"><p>Die Einzahlungen konnten nicht gespeichert werden.</p></div>
|
<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; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if ($erfolgsmeldung !== null): ?>
|
||||||
|
<div class="hint-box success"><p><?php echo saas_html($erfolgsmeldung); ?></p></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="hint-box">
|
<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>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>
|
||||||
|
|||||||
+55
-19
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
include "functions.php";
|
include "functions.php";
|
||||||
require_once __DIR__ . "/app/ledger.php";
|
require_once __DIR__ . "/app/ledger.php";
|
||||||
require_once __DIR__ . "/app/notices.php";
|
require_once __DIR__ . "/app/notices.php";
|
||||||
@@ -32,7 +34,15 @@ if ($saasUser !== null && saas_user_has_role(['owner', 'admin'], $saasUser)) {
|
|||||||
|
|
||||||
if($hasAccess){
|
if($hasAccess){
|
||||||
|
|
||||||
echo "<h2>Kaffeeliste - Hinweise</h2>";
|
$meldung = null;
|
||||||
|
$fehler = null;
|
||||||
|
|
||||||
|
if (isset($_SESSION['flash_hinweise']) && is_array($_SESSION['flash_hinweise'])) {
|
||||||
|
$flash = $_SESSION['flash_hinweise'];
|
||||||
|
unset($_SESSION['flash_hinweise']);
|
||||||
|
$meldung = isset($flash['meldung']) ? (string)$flash['meldung'] : null;
|
||||||
|
$fehler = isset($flash['fehler']) ? (string)$flash['fehler'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
// Hinweis speichern oder als geloescht markieren
|
// Hinweis speichern oder als geloescht markieren
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
@@ -44,26 +54,50 @@ if($hasAccess){
|
|||||||
$id = (int)($_POST['id'] ?? 0);
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
if ($id > 0 && notices_soft_delete($pdo, $tenantId, $id)) {
|
if ($id > 0 && notices_soft_delete($pdo, $tenantId, $id)) {
|
||||||
app_audit_log($pdo, $tenantId, $actorUserId, 'notice.deleted', 'notice', $id);
|
app_audit_log($pdo, $tenantId, $actorUserId, 'notice.deleted', 'notice', $id);
|
||||||
|
$meldung = 'Hinweis wurde gelöscht.';
|
||||||
|
} else {
|
||||||
|
$fehler = 'Der Hinweis konnte nicht gelöscht werden.';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$nachricht = $_POST['nachricht'] ?? '';
|
$nachricht = trim((string)($_POST['nachricht'] ?? ''));
|
||||||
$gueltig_bis = $_POST['gueltig_bis'] ?? ''; // z.B. "2025-09-03T14:00"
|
$gueltig_bis = trim((string)($_POST['gueltig_bis'] ?? '')); // z.B. "2025-09-03T14:00"
|
||||||
$dt = DateTime::createFromFormat('Y-m-d\TH:i', $gueltig_bis);
|
$dt = DateTime::createFromFormat('Y-m-d\TH:i', $gueltig_bis);
|
||||||
|
$dtErrors = DateTime::getLastErrors();
|
||||||
|
|
||||||
if ($dt) {
|
if ($nachricht === '') {
|
||||||
|
$fehler = 'Bitte eine Nachricht eintragen.';
|
||||||
|
} elseif ($dt === false || ($dtErrors !== false && ((int)$dtErrors['warning_count'] > 0 || (int)$dtErrors['error_count'] > 0))) {
|
||||||
|
$fehler = 'Bitte ein gültiges Datum mit Uhrzeit angeben.';
|
||||||
|
} else {
|
||||||
$gueltig_bis_sql = $dt->format('Y-m-d H:i:s'); // z.B. "2025-09-03 14:00:00"
|
$gueltig_bis_sql = $dt->format('Y-m-d H:i:s'); // z.B. "2025-09-03 14:00:00"
|
||||||
notices_create($pdo, $tenantId, (string)$nachricht, $gueltig_bis_sql, $actorUserId);
|
notices_create($pdo, $tenantId, (string)$nachricht, $gueltig_bis_sql, $actorUserId);
|
||||||
app_audit_log($pdo, $tenantId, $actorUserId, 'notice.created', 'notice', null, ['valid_until' => $gueltig_bis_sql]);
|
app_audit_log($pdo, $tenantId, $actorUserId, 'notice.created', 'notice', null, ['valid_until' => $gueltig_bis_sql]);
|
||||||
|
$meldung = 'Hinweis wurde gespeichert.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$_SESSION['flash_hinweise'] = [
|
||||||
|
'meldung' => $meldung,
|
||||||
|
'fehler' => $fehler,
|
||||||
|
];
|
||||||
|
header('Location: hinweise.php');
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hinweise = notices_fetch_all($pdo, $tenantId);
|
$hinweise = notices_fetch_all($pdo, $tenantId);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<h2>Kaffeeliste - Hinweise</h2>
|
||||||
|
|
||||||
|
<?php if ($meldung !== null): ?>
|
||||||
|
<div class="hint-box success"><p><?php echo saas_html($meldung); ?></p></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($fehler !== null): ?>
|
||||||
|
<div class="hint-box error"><p><?php echo saas_html($fehler); ?></p></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<h2>Neuen Hinweis hinzufügen</h2>
|
<h2>Neuen Hinweis hinzufügen</h2>
|
||||||
<form method="post">
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||||
<input type="hidden" name="aktion" value="speichern">
|
<input type="hidden" name="aktion" value="speichern">
|
||||||
<?php echo app_csrf_field(); ?>
|
<?php echo app_csrf_field(); ?>
|
||||||
<label>Nachricht:</label>
|
<label>Nachricht:</label>
|
||||||
@@ -74,20 +108,22 @@ if($hasAccess){
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2>Alle Hinweise</h2>
|
<h2>Alle Hinweise</h2>
|
||||||
<?php foreach ($hinweise as $hinweis): ?>
|
<?php if ($hinweise === []): ?>
|
||||||
<div class="hinweis">
|
<p>Aktuell sind keine Hinweise angelegt.</p>
|
||||||
<strong><?php echo saas_html($hinweis['message']); ?></strong><br>
|
<?php else: ?>
|
||||||
<small>Gültig bis: <?php echo saas_html((new DateTimeImmutable($hinweis['valid_until']))->format('d.m.Y H:i')); ?></small><br>
|
<?php foreach ($hinweise as $hinweis): ?>
|
||||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" onsubmit="return confirm('Diesen Hinweis wirklich löschen?')">
|
<div class="hinweis">
|
||||||
<input type="hidden" name="aktion" value="loeschen">
|
<strong><?php echo saas_html($hinweis['message']); ?></strong><br>
|
||||||
<input type="hidden" name="id" value="<?php echo (int)$hinweis['id']; ?>">
|
<small>Gültig bis: <?php echo saas_html((new DateTimeImmutable($hinweis['valid_until']))->format('d.m.Y H:i')); ?></small><br>
|
||||||
<?php echo app_csrf_field(); ?>
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" onsubmit="return confirm('Diesen Hinweis wirklich löschen?')">
|
||||||
<button type="submit">Löschen</button>
|
<input type="hidden" name="aktion" value="loeschen">
|
||||||
</form>
|
<input type="hidden" name="id" value="<?php echo (int)$hinweis['id']; ?>">
|
||||||
</div>
|
<?php echo app_csrf_field(); ?>
|
||||||
<?php endforeach; ?>
|
<button type="submit">Löschen</button>
|
||||||
</body>
|
</form>
|
||||||
</html>
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|||||||
+19
-13
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
include "functions.php";
|
include "functions.php";
|
||||||
require_once __DIR__ . "/app/ledger.php";
|
require_once __DIR__ . "/app/ledger.php";
|
||||||
@@ -40,6 +41,14 @@ if($hasAccess){
|
|||||||
$bearbeitenId = null;
|
$bearbeitenId = null;
|
||||||
$actorUserId = $saasUser['user_id'] ?? null;
|
$actorUserId = $saasUser['user_id'] ?? null;
|
||||||
|
|
||||||
|
if (isset($_SESSION['flash_mitglieder']) && is_array($_SESSION['flash_mitglieder'])) {
|
||||||
|
$flash = $_SESSION['flash_mitglieder'];
|
||||||
|
unset($_SESSION['flash_mitglieder']);
|
||||||
|
$meldung = isset($flash['meldung']) ? (string)$flash['meldung'] : null;
|
||||||
|
$fehler = isset($flash['fehler']) ? (string)$flash['fehler'] : null;
|
||||||
|
$einladungslink = isset($flash['einladungslink']) ? (string)$flash['einladungslink'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
$aktion = $_POST["aktion"] ?? '';
|
$aktion = $_POST["aktion"] ?? '';
|
||||||
|
|
||||||
@@ -208,6 +217,16 @@ if($hasAccess){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($aktion !== 'bearbeiten') {
|
||||||
|
$_SESSION['flash_mitglieder'] = [
|
||||||
|
'meldung' => $meldung,
|
||||||
|
'fehler' => $fehler,
|
||||||
|
'einladungslink' => $einladungslink,
|
||||||
|
];
|
||||||
|
header('Location: mitarbeiterverwalten.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -227,15 +246,6 @@ if($hasAccess){
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Kaffeeliste - Mitglieder verwalten</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>Mitglieder verwalten</h2>
|
<h2>Mitglieder verwalten</h2>
|
||||||
|
|
||||||
<?php if ($meldung !== null): ?>
|
<?php if ($meldung !== null): ?>
|
||||||
@@ -415,10 +425,6 @@ if($hasAccess){
|
|||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (PHP_SAPI !== 'cli') {
|
||||||
|
http_response_code(403);
|
||||||
|
exit("Dieses Skript ist nur fuer die Kommandozeile gedacht.\n");
|
||||||
|
}
|
||||||
|
|
||||||
require __DIR__ . '/dev-db.php';
|
require __DIR__ . '/dev-db.php';
|
||||||
|
|
||||||
$baseUrl = rtrim((string)(getenv('SMOKE_BASE_URL') ?: 'http://127.0.0.1:8080'), '/');
|
$baseUrl = rtrim((string)(getenv('SMOKE_BASE_URL') ?: 'http://127.0.0.1:8080'), '/');
|
||||||
@@ -11,7 +16,7 @@ $password = 'BillingCapTest123!';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string,string> $cookies
|
* @param array<string,string> $cookies
|
||||||
* @return array{status:int, body:string}
|
* @return array{status:int, body:string, location:?string}
|
||||||
*/
|
*/
|
||||||
function bc_request(string $url, array &$cookies, string $method = 'GET', ?string $postBody = null): array
|
function bc_request(string $url, array &$cookies, string $method = 'GET', ?string $postBody = null): array
|
||||||
{
|
{
|
||||||
@@ -43,6 +48,7 @@ function bc_request(string $url, array &$cookies, string $method = 'GET', ?strin
|
|||||||
$body = @file_get_contents($url, false, $context);
|
$body = @file_get_contents($url, false, $context);
|
||||||
$responseHeaders = $http_response_header ?? [];
|
$responseHeaders = $http_response_header ?? [];
|
||||||
$status = 0;
|
$status = 0;
|
||||||
|
$location = null;
|
||||||
|
|
||||||
foreach ($responseHeaders as $header) {
|
foreach ($responseHeaders as $header) {
|
||||||
if (preg_match('~^HTTP/\S+\s+(\d{3})~', $header, $m) === 1) {
|
if (preg_match('~^HTTP/\S+\s+(\d{3})~', $header, $m) === 1) {
|
||||||
@@ -51,9 +57,28 @@ function bc_request(string $url, array &$cookies, string $method = 'GET', ?strin
|
|||||||
if (preg_match('/^Set-Cookie:\s*([^=;]+)=([^;]+)/i', $header, $m) === 1) {
|
if (preg_match('/^Set-Cookie:\s*([^=;]+)=([^;]+)/i', $header, $m) === 1) {
|
||||||
$cookies[$m[1]] = $m[2];
|
$cookies[$m[1]] = $m[2];
|
||||||
}
|
}
|
||||||
|
if (preg_match('/^Location:\s*(.+)$/i', $header, $m) === 1) {
|
||||||
|
$location = trim($m[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['status' => $status, 'body' => (string)$body];
|
return ['status' => $status, 'body' => (string)$body, 'location' => $location];
|
||||||
|
}
|
||||||
|
|
||||||
|
function bc_follow_redirect(array $response, array &$cookies, string $baseUrl): array
|
||||||
|
{
|
||||||
|
if ($response['status'] < 300 || $response['status'] >= 400 || $response['location'] === null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = (string)$response['location'];
|
||||||
|
if (preg_match('~^https?://~i', $location) === 1) {
|
||||||
|
$url = $location;
|
||||||
|
} else {
|
||||||
|
$url = $baseUrl . '/' . ltrim($location, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return bc_request($url, $cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bc_csrf(string $body): string
|
function bc_csrf(string $body): string
|
||||||
@@ -121,38 +146,38 @@ bc_request("{$baseUrl}/login.php", $cookies, 'POST', http_build_query([
|
|||||||
|
|
||||||
// 9 aktive Teilnehmer vorhanden (Cap 10) -> ein aktives Anlegen muss klappen (10/10).
|
// 9 aktive Teilnehmer vorhanden (Cap 10) -> ein aktives Anlegen muss klappen (10/10).
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$body10 = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$body10 = bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'anlegen',
|
'aktion' => 'anlegen',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'name' => 'Zehntes Mitglied',
|
'name' => 'Zehntes Mitglied',
|
||||||
'email' => "billingcap-tenth-{$suffix}@test.local",
|
'email' => "billingcap-tenth-{$suffix}@test.local",
|
||||||
'paypalname' => '',
|
'paypalname' => '',
|
||||||
'aktiv' => '1',
|
'aktiv' => '1',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
bc_assert('10. aktives Mitglied bei Cap 10 wird angelegt', str_contains($body10['body'], 'wurde angelegt'), $failures, $passes);
|
bc_assert('10. aktives Mitglied bei Cap 10 wird angelegt', str_contains($body10['body'], 'wurde angelegt'), $failures, $passes);
|
||||||
|
|
||||||
// Jetzt bei 10/10 -> ein weiteres aktives Anlegen muss abgelehnt werden.
|
// Jetzt bei 10/10 -> ein weiteres aktives Anlegen muss abgelehnt werden.
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$body11 = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$body11 = bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'anlegen',
|
'aktion' => 'anlegen',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'name' => 'Elftes Mitglied',
|
'name' => 'Elftes Mitglied',
|
||||||
'email' => "billingcap-eleventh-{$suffix}@test.local",
|
'email' => "billingcap-eleventh-{$suffix}@test.local",
|
||||||
'paypalname' => '',
|
'paypalname' => '',
|
||||||
'aktiv' => '1',
|
'aktiv' => '1',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
bc_assert('11. aktives Mitglied bei Cap 10 wird abgelehnt', str_contains($body11['body'], 'maximal 10 aktive Mitglieder'), $failures, $passes);
|
bc_assert('11. aktives Mitglied bei Cap 10 wird abgelehnt', str_contains($body11['body'], 'maximal 10 aktive Mitglieder'), $failures, $passes);
|
||||||
bc_assert('11. Mitglied wurde nicht in der DB angelegt', (int)$pdo->query("SELECT COUNT(*) FROM participants WHERE tenant_id = {$tenantId} AND email = 'billingcap-eleventh-{$suffix}@test.local'")->fetchColumn() === 0, $failures, $passes);
|
bc_assert('11. Mitglied wurde nicht in der DB angelegt', (int)$pdo->query("SELECT COUNT(*) FROM participants WHERE tenant_id = {$tenantId} AND email = 'billingcap-eleventh-{$suffix}@test.local'")->fetchColumn() === 0, $failures, $passes);
|
||||||
|
|
||||||
// Inaktives Anlegen bei vollem Cap muss weiterhin moeglich sein.
|
// Inaktives Anlegen bei vollem Cap muss weiterhin moeglich sein.
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$bodyInactive = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$bodyInactive = bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'anlegen',
|
'aktion' => 'anlegen',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'name' => 'Inaktives Mitglied',
|
'name' => 'Inaktives Mitglied',
|
||||||
'email' => "billingcap-inactive-{$suffix}@test.local",
|
'email' => "billingcap-inactive-{$suffix}@test.local",
|
||||||
'paypalname' => '',
|
'paypalname' => '',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
bc_assert('Inaktives Mitglied bei vollem Cap wird angelegt', str_contains($bodyInactive['body'], 'wurde angelegt'), $failures, $passes);
|
bc_assert('Inaktives Mitglied bei vollem Cap wird angelegt', str_contains($bodyInactive['body'], 'wurde angelegt'), $failures, $passes);
|
||||||
|
|
||||||
$inactiveId = (int)$pdo->query("SELECT id FROM participants WHERE tenant_id = {$tenantId} AND email = 'billingcap-inactive-{$suffix}@test.local'")->fetchColumn();
|
$inactiveId = (int)$pdo->query("SELECT id FROM participants WHERE tenant_id = {$tenantId} AND email = 'billingcap-inactive-{$suffix}@test.local'")->fetchColumn();
|
||||||
@@ -160,45 +185,45 @@ bc_assert('Neu angelegtes Mitglied ist tatsaechlich inaktiv', (int)$pdo->query("
|
|||||||
|
|
||||||
// Aktivieren des inaktiven Mitglieds bei vollem Cap muss abgelehnt werden.
|
// Aktivieren des inaktiven Mitglieds bei vollem Cap muss abgelehnt werden.
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$bodyActivateBlocked = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$bodyActivateBlocked = bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'aktivieren',
|
'aktion' => 'aktivieren',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'mitgliedID' => (string)$inactiveId,
|
'mitgliedID' => (string)$inactiveId,
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
bc_assert('Aktivieren bei vollem Cap wird abgelehnt', str_contains($bodyActivateBlocked['body'], 'maximal 10 aktive Mitglieder'), $failures, $passes);
|
bc_assert('Aktivieren bei vollem Cap wird abgelehnt', str_contains($bodyActivateBlocked['body'], 'maximal 10 aktive Mitglieder'), $failures, $passes);
|
||||||
bc_assert('Mitglied ist nach abgelehntem Aktivieren weiterhin inaktiv', (int)$pdo->query("SELECT active FROM participants WHERE id = {$inactiveId}")->fetchColumn() === 0, $failures, $passes);
|
bc_assert('Mitglied ist nach abgelehntem Aktivieren weiterhin inaktiv', (int)$pdo->query("SELECT active FROM participants WHERE id = {$inactiveId}")->fetchColumn() === 0, $failures, $passes);
|
||||||
|
|
||||||
// Ein aktives Mitglied deaktivieren (Platz schaffen), dann klappt Aktivieren.
|
// Ein aktives Mitglied deaktivieren (Platz schaffen), dann klappt Aktivieren.
|
||||||
$freeUpId = $extraParticipantIds[0];
|
$freeUpId = $extraParticipantIds[0];
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'deaktivieren',
|
'aktion' => 'deaktivieren',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'mitgliedID' => (string)$freeUpId,
|
'mitgliedID' => (string)$freeUpId,
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
|
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$bodyActivateOk = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$bodyActivateOk = bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'aktivieren',
|
'aktion' => 'aktivieren',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'mitgliedID' => (string)$inactiveId,
|
'mitgliedID' => (string)$inactiveId,
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
bc_assert('Aktivieren nach Platzschaffen (Deaktivierung) klappt', str_contains($bodyActivateOk['body'], 'wurde aktiviert'), $failures, $passes);
|
bc_assert('Aktivieren nach Platzschaffen (Deaktivierung) klappt', str_contains($bodyActivateOk['body'], 'wurde aktiviert'), $failures, $passes);
|
||||||
|
|
||||||
// bearbeitenspeichern: ein weiteres inaktives Mitglied anlegen, dann per
|
// bearbeitenspeichern: ein weiteres inaktives Mitglied anlegen, dann per
|
||||||
// Bearbeiten-Formular aktivieren versuchen, wieder bei vollem Cap (10/10).
|
// Bearbeiten-Formular aktivieren versuchen, wieder bei vollem Cap (10/10).
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'anlegen',
|
'aktion' => 'anlegen',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'name' => 'Editier-Testmitglied',
|
'name' => 'Editier-Testmitglied',
|
||||||
'email' => "billingcap-edit-{$suffix}@test.local",
|
'email' => "billingcap-edit-{$suffix}@test.local",
|
||||||
'paypalname' => '',
|
'paypalname' => '',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
$editId = (int)$pdo->query("SELECT id FROM participants WHERE tenant_id = {$tenantId} AND email = 'billingcap-edit-{$suffix}@test.local'")->fetchColumn();
|
$editId = (int)$pdo->query("SELECT id FROM participants WHERE tenant_id = {$tenantId} AND email = 'billingcap-edit-{$suffix}@test.local'")->fetchColumn();
|
||||||
|
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$bodyEditBlocked = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$bodyEditBlocked = bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'bearbeitenspeichern',
|
'aktion' => 'bearbeitenspeichern',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'mitgliedID' => (string)$editId,
|
'mitgliedID' => (string)$editId,
|
||||||
@@ -206,19 +231,19 @@ $bodyEditBlocked = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, '
|
|||||||
'email' => "billingcap-edit-{$suffix}@test.local",
|
'email' => "billingcap-edit-{$suffix}@test.local",
|
||||||
'paypalname' => '',
|
'paypalname' => '',
|
||||||
'aktiv' => '1',
|
'aktiv' => '1',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
bc_assert('bearbeitenspeichern-Reaktivierung bei vollem Cap wird abgelehnt', str_contains($bodyEditBlocked['body'], 'maximal 10 aktive Mitglieder'), $failures, $passes);
|
bc_assert('bearbeitenspeichern-Reaktivierung bei vollem Cap wird abgelehnt', str_contains($bodyEditBlocked['body'], 'maximal 10 aktive Mitglieder'), $failures, $passes);
|
||||||
|
|
||||||
// Editieren ohne Statuswechsel (bleibt inaktiv) muss weiterhin klappen.
|
// Editieren ohne Statuswechsel (bleibt inaktiv) muss weiterhin klappen.
|
||||||
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$page = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$bodyEditOk = bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$bodyEditOk = bc_follow_redirect(bc_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'aktion' => 'bearbeitenspeichern',
|
'aktion' => 'bearbeitenspeichern',
|
||||||
'csrf_token' => bc_csrf($page['body']),
|
'csrf_token' => bc_csrf($page['body']),
|
||||||
'mitgliedID' => (string)$editId,
|
'mitgliedID' => (string)$editId,
|
||||||
'name' => 'Editier-Testmitglied Neu',
|
'name' => 'Editier-Testmitglied Neu',
|
||||||
'email' => "billingcap-edit-{$suffix}@test.local",
|
'email' => "billingcap-edit-{$suffix}@test.local",
|
||||||
'paypalname' => '',
|
'paypalname' => '',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
bc_assert('bearbeitenspeichern ohne Aktivierung bei vollem Cap klappt', str_contains($bodyEditOk['body'], 'wurde gespeichert'), $failures, $passes);
|
bc_assert('bearbeitenspeichern ohne Aktivierung bei vollem Cap klappt', str_contains($bodyEditOk['body'], 'wurde gespeichert'), $failures, $passes);
|
||||||
|
|
||||||
// Aufraeumen
|
// Aufraeumen
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (PHP_SAPI !== 'cli') {
|
||||||
|
http_response_code(403);
|
||||||
|
exit("Dieses Skript ist nur fuer die Kommandozeile gedacht.\n");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prueft die vier Kontofunktionen, die vor dem Go-Live ergaenzt wurden:
|
* Prueft die vier Kontofunktionen, die vor dem Go-Live ergaenzt wurden:
|
||||||
*
|
*
|
||||||
@@ -78,6 +83,22 @@ function konto_request(string $url, array &$cookies, string $method = 'GET', ?st
|
|||||||
return ['status' => $status, 'body' => (string)$body, 'location' => $location];
|
return ['status' => $status, 'body' => (string)$body, 'location' => $location];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function konto_follow_redirect(array $response, array &$cookies, string $baseUrl): array
|
||||||
|
{
|
||||||
|
if ($response['status'] < 300 || $response['status'] >= 400 || $response['location'] === null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = (string)$response['location'];
|
||||||
|
if (preg_match('~^https?://~i', $location) === 1) {
|
||||||
|
$url = $location;
|
||||||
|
} else {
|
||||||
|
$url = $baseUrl . '/' . ltrim($location, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return konto_request($url, $cookies);
|
||||||
|
}
|
||||||
|
|
||||||
/** @param array<string,string> $cookies */
|
/** @param array<string,string> $cookies */
|
||||||
function konto_csrf(string $url, array &$cookies): string
|
function konto_csrf(string $url, array &$cookies): string
|
||||||
{
|
{
|
||||||
@@ -160,12 +181,12 @@ try {
|
|||||||
// 3. Verifikation wird fuer Einladungen erzwungen
|
// 3. Verifikation wird fuer Einladungen erzwungen
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
$csrf = konto_csrf("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$csrf = konto_csrf("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$einladung = konto_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$einladung = konto_follow_redirect(konto_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'csrf_token' => $csrf,
|
'csrf_token' => $csrf,
|
||||||
'aktion' => 'zugang_gewaehren',
|
'aktion' => 'zugang_gewaehren',
|
||||||
'mitgliedID' => (string)$participantId,
|
'mitgliedID' => (string)$participantId,
|
||||||
'rolle' => 'member',
|
'rolle' => 'member',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
pruefe(
|
pruefe(
|
||||||
'Einladung ohne bestaetigte Adresse wird abgelehnt',
|
'Einladung ohne bestaetigte Adresse wird abgelehnt',
|
||||||
str_contains($einladung['body'], 'bestätigt sein')
|
str_contains($einladung['body'], 'bestätigt sein')
|
||||||
@@ -179,12 +200,12 @@ try {
|
|||||||
$pdo->prepare('UPDATE users SET email_verified_at = NOW() WHERE id = ?')->execute([$userId]);
|
$pdo->prepare('UPDATE users SET email_verified_at = NOW() WHERE id = ?')->execute([$userId]);
|
||||||
|
|
||||||
$csrf = konto_csrf("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
$csrf = konto_csrf("{$baseUrl}/mitarbeiterverwalten.php", $cookies);
|
||||||
$einladung = konto_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
$einladung = konto_follow_redirect(konto_request("{$baseUrl}/mitarbeiterverwalten.php", $cookies, 'POST', http_build_query([
|
||||||
'csrf_token' => $csrf,
|
'csrf_token' => $csrf,
|
||||||
'aktion' => 'zugang_gewaehren',
|
'aktion' => 'zugang_gewaehren',
|
||||||
'mitgliedID' => (string)$participantId,
|
'mitgliedID' => (string)$participantId,
|
||||||
'rolle' => 'member',
|
'rolle' => 'member',
|
||||||
]));
|
])), $cookies, $baseUrl);
|
||||||
pruefe(
|
pruefe(
|
||||||
'Einladung mit bestaetigter Adresse funktioniert',
|
'Einladung mit bestaetigter Adresse funktioniert',
|
||||||
str_contains($einladung['body'], 'Zugang wurde gewährt')
|
str_contains($einladung['body'], 'Zugang wurde gewährt')
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (PHP_SAPI !== 'cli') {
|
||||||
|
http_response_code(403);
|
||||||
|
exit("Dieses Skript ist nur fuer die Kommandozeile gedacht.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . '/dev-db.php';
|
||||||
|
require __DIR__ . '/../app/imports.php';
|
||||||
|
|
||||||
|
function payment_matching_assert(string $label, bool $condition, array &$failures, int &$passes): void
|
||||||
|
{
|
||||||
|
if ($condition) {
|
||||||
|
$passes++;
|
||||||
|
echo "PASS {$label}\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$failures[] = $label;
|
||||||
|
echo "FAIL {$label}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function payment_matching_insert_tenant(PDO $pdo, string $suffix, string $key): int
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO tenants (slug, name, status) VALUES (?, ?, ?)');
|
||||||
|
$stmt->execute(["payment-match-{$key}-{$suffix}", "Payment Match {$key}", 'active']);
|
||||||
|
|
||||||
|
return (int)$pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
function payment_matching_insert_participant(PDO $pdo, int $tenantId, string $displayName, string $email, ?string $paypalName): int
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'INSERT INTO participants (tenant_id, display_name, email, email_norm, paypal_name, active) VALUES (?, ?, ?, ?, ?, 1)'
|
||||||
|
);
|
||||||
|
$stmt->execute([$tenantId, $displayName, $email, strtolower($email), $paypalName]);
|
||||||
|
|
||||||
|
return (int)$pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
function payment_matching_cleanup(PDO $pdo, array $tenantIds): void
|
||||||
|
{
|
||||||
|
foreach ($tenantIds as $tenantId) {
|
||||||
|
$pdo->prepare('DELETE FROM participants WHERE tenant_id = ?')->execute([$tenantId]);
|
||||||
|
$pdo->prepare('DELETE FROM tenants WHERE id = ?')->execute([$tenantId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = dev_pdo();
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
$failures = [];
|
||||||
|
$passes = 0;
|
||||||
|
$tenantIds = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$tenantId = payment_matching_insert_tenant($pdo, $suffix, 'a');
|
||||||
|
$otherTenantId = payment_matching_insert_tenant($pdo, $suffix, 'b');
|
||||||
|
$tenantIds = [$tenantId, $otherTenantId];
|
||||||
|
|
||||||
|
$uniqueId = payment_matching_insert_participant(
|
||||||
|
$pdo,
|
||||||
|
$tenantId,
|
||||||
|
"Eindeutig {$suffix}",
|
||||||
|
"unique-{$suffix}@test.local",
|
||||||
|
"PayPal Unique {$suffix}"
|
||||||
|
);
|
||||||
|
payment_matching_insert_participant(
|
||||||
|
$pdo,
|
||||||
|
$tenantId,
|
||||||
|
"Doppelt {$suffix}",
|
||||||
|
"duplicate-one-{$suffix}@test.local",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
payment_matching_insert_participant(
|
||||||
|
$pdo,
|
||||||
|
$tenantId,
|
||||||
|
"Doppelt {$suffix}",
|
||||||
|
"duplicate-two-{$suffix}@test.local",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
payment_matching_insert_participant(
|
||||||
|
$pdo,
|
||||||
|
$tenantId,
|
||||||
|
"Paypal A {$suffix}",
|
||||||
|
"paypal-a-{$suffix}@test.local",
|
||||||
|
"PayPal Doppelt {$suffix}"
|
||||||
|
);
|
||||||
|
payment_matching_insert_participant(
|
||||||
|
$pdo,
|
||||||
|
$tenantId,
|
||||||
|
"Paypal B {$suffix}",
|
||||||
|
"paypal-b-{$suffix}@test.local",
|
||||||
|
"PayPal Doppelt {$suffix}"
|
||||||
|
);
|
||||||
|
$otherTenantUniqueId = payment_matching_insert_participant(
|
||||||
|
$pdo,
|
||||||
|
$otherTenantId,
|
||||||
|
"Doppelt {$suffix}",
|
||||||
|
"other-{$suffix}@test.local",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
$uniqueByName = imports_find_participant($pdo, $tenantId, "Eindeutig {$suffix}");
|
||||||
|
payment_matching_assert(
|
||||||
|
'eindeutiger Anzeigename wird gefunden',
|
||||||
|
$uniqueByName !== null && $uniqueByName['participant_id'] === $uniqueId,
|
||||||
|
$failures,
|
||||||
|
$passes
|
||||||
|
);
|
||||||
|
|
||||||
|
$uniqueByPaypalName = imports_find_participant($pdo, $tenantId, "paypal unique {$suffix}");
|
||||||
|
payment_matching_assert(
|
||||||
|
'eindeutiger PayPal-Name wird case-insensitive gefunden',
|
||||||
|
$uniqueByPaypalName !== null && $uniqueByPaypalName['participant_id'] === $uniqueId,
|
||||||
|
$failures,
|
||||||
|
$passes
|
||||||
|
);
|
||||||
|
|
||||||
|
payment_matching_assert(
|
||||||
|
'doppelter Anzeigename bleibt unmatched',
|
||||||
|
imports_find_participant($pdo, $tenantId, "Doppelt {$suffix}") === null,
|
||||||
|
$failures,
|
||||||
|
$passes
|
||||||
|
);
|
||||||
|
|
||||||
|
payment_matching_assert(
|
||||||
|
'doppelter PayPal-Name bleibt unmatched',
|
||||||
|
imports_find_participant($pdo, $tenantId, "PayPal Doppelt {$suffix}") === null,
|
||||||
|
$failures,
|
||||||
|
$passes
|
||||||
|
);
|
||||||
|
|
||||||
|
$otherTenantMatch = imports_find_participant($pdo, $otherTenantId, "Doppelt {$suffix}");
|
||||||
|
payment_matching_assert(
|
||||||
|
'gleicher Name in anderem Mandanten bleibt eindeutig',
|
||||||
|
$otherTenantMatch !== null && $otherTenantMatch['participant_id'] === $otherTenantUniqueId,
|
||||||
|
$failures,
|
||||||
|
$passes
|
||||||
|
);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
fwrite(STDERR, "Payment matching check failed to run: {$e->getMessage()}\n");
|
||||||
|
payment_matching_cleanup($pdo, $tenantIds);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
payment_matching_cleanup($pdo, $tenantIds);
|
||||||
|
|
||||||
|
if ($failures !== []) {
|
||||||
|
echo "\nPayment matching check failed with " . count($failures) . " failure(s):\n";
|
||||||
|
foreach ($failures as $failure) {
|
||||||
|
echo "- {$failure}\n";
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\nPayment matching check passed with {$passes} assertions.\n";
|
||||||
@@ -47,6 +47,8 @@ foreach ($tenants as $tenantIdRaw) {
|
|||||||
$schwelleCents = (int)$settings['negative_warning_cents']; // <= 0
|
$schwelleCents = (int)$settings['negative_warning_cents']; // <= 0
|
||||||
$intervallTage = max(1, (int)$settings['payment_reminder_interval_days']);
|
$intervallTage = max(1, (int)$settings['payment_reminder_interval_days']);
|
||||||
$jetzt = new DateTimeImmutable('now');
|
$jetzt = new DateTimeImmutable('now');
|
||||||
|
$tenantGesendet = 0;
|
||||||
|
$tenantUebersprungen = 0;
|
||||||
|
|
||||||
$teilnehmer = ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
|
$teilnehmer = ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
|
||||||
$letzteErinnerungen = saas_fetch_last_payment_reminders($pdo, $tenantId);
|
$letzteErinnerungen = saas_fetch_last_payment_reminders($pdo, $tenantId);
|
||||||
@@ -68,11 +70,13 @@ foreach ($tenants as $tenantIdRaw) {
|
|||||||
try {
|
try {
|
||||||
$letzte = new DateTimeImmutable($letzteErinnerungen[$participantId]);
|
$letzte = new DateTimeImmutable($letzteErinnerungen[$participantId]);
|
||||||
if ($letzte->add(new DateInterval("P{$intervallTage}D")) > $jetzt) {
|
if ($letzte->add(new DateInterval("P{$intervallTage}D")) > $jetzt) {
|
||||||
|
$tenantUebersprungen++;
|
||||||
$gesamtUebersprungen++;
|
$gesamtUebersprungen++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
// Unparsebares Datum: sicherheitshalber ueberspringen.
|
// Unparsebares Datum: sicherheitshalber ueberspringen.
|
||||||
|
$tenantUebersprungen++;
|
||||||
$gesamtUebersprungen++;
|
$gesamtUebersprungen++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -84,6 +88,7 @@ foreach ($tenants as $tenantIdRaw) {
|
|||||||
|
|
||||||
if ($dryRun) {
|
if ($dryRun) {
|
||||||
echo "[dry-run] Mandant {$tenantId}: {$person['display_name']} <{$email}> offen " . saas_format_money_cents($debtCents) . " EUR\n";
|
echo "[dry-run] Mandant {$tenantId}: {$person['display_name']} <{$email}> offen " . saas_format_money_cents($debtCents) . " EUR\n";
|
||||||
|
$tenantGesendet++;
|
||||||
$gesamtGesendet++;
|
$gesamtGesendet++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -94,12 +99,16 @@ foreach ($tenants as $tenantIdRaw) {
|
|||||||
$sendResult['ok'] ? 'sent' : 'failed', $sendResult['error'] ?? null, null
|
$sendResult['ok'] ? 'sent' : 'failed', $sendResult['error'] ?? null, null
|
||||||
);
|
);
|
||||||
if ($sendResult['ok']) {
|
if ($sendResult['ok']) {
|
||||||
|
$tenantGesendet++;
|
||||||
$gesamtGesendet++;
|
$gesamtGesendet++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$dryRun) {
|
if (!$dryRun) {
|
||||||
app_audit_log($pdo, $tenantId, null, 'payment_reminder.batch', 'tenant', $tenantId, ['gesendet' => $gesamtGesendet]);
|
app_audit_log($pdo, $tenantId, null, 'payment_reminder.batch', 'tenant', $tenantId, [
|
||||||
|
'gesendet' => $tenantGesendet,
|
||||||
|
'uebersprungen' => $tenantUebersprungen,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+107
-31
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
include "functions.php";
|
include "functions.php";
|
||||||
require_once __DIR__ . "/app/ledger.php";
|
require_once __DIR__ . "/app/ledger.php";
|
||||||
app_require_csrf();
|
app_require_csrf();
|
||||||
@@ -57,64 +59,137 @@ function stricheintragen_fetch_participants(PDO $pdo, int $tenantId, string $act
|
|||||||
$eingetragen = 0;
|
$eingetragen = 0;
|
||||||
$fehlgeschlagen = false;
|
$fehlgeschlagen = false;
|
||||||
$hatGespeichert = false;
|
$hatGespeichert = false;
|
||||||
|
$erfolgsmeldung = null;
|
||||||
|
$validierungsFehler = [];
|
||||||
|
$eingaben = [];
|
||||||
|
$kostenproStrichEingabe = null;
|
||||||
|
$action = (string)($_GET['action'] ?? 'alle');
|
||||||
|
if (!in_array($action, ['vorderseite', 'rueckseite', 'alle'], true)) {
|
||||||
|
$action = 'alle';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_SESSION['flash_stricheintragen']) && is_array($_SESSION['flash_stricheintragen'])) {
|
||||||
|
$flash = $_SESSION['flash_stricheintragen'];
|
||||||
|
unset($_SESSION['flash_stricheintragen']);
|
||||||
|
if (($flash['type'] ?? '') === 'success') {
|
||||||
|
$erfolgsmeldung = sprintf('%d Einträge erfolgreich hinzugefügt.', (int)($flash['count'] ?? 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const STRICHE_MAX_PRO_PERSON = 50;
|
||||||
|
const STRICHE_MAX_PREIS_CENTS = 10000;
|
||||||
|
|
||||||
// Verarbeitung des Formulars, wenn es gesendet wurde
|
// Verarbeitung des Formulars, wenn es gesendet wurde
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
$hatGespeichert = true;
|
$hatGespeichert = true;
|
||||||
$kostenproStrich = floatval($_POST["kostenproStrich"] ?? 0);
|
$action = (string)($_POST['liste_action'] ?? 'alle');
|
||||||
$unitPriceCents = (int)round($kostenproStrich * 100);
|
if (!in_array($action, ['vorderseite', 'rueckseite', 'alle'], true)) {
|
||||||
|
$action = 'alle';
|
||||||
|
}
|
||||||
|
$kostenproStrichRaw = trim((string)($_POST["kostenproStrich"] ?? ''));
|
||||||
|
$kostenproStrichEingabe = $kostenproStrichRaw;
|
||||||
|
$unitPriceCents = saas_parse_money_cents($kostenproStrichRaw);
|
||||||
|
|
||||||
|
if ($unitPriceCents === null || $unitPriceCents <= 0) {
|
||||||
|
$validierungsFehler[] = 'Bitte gültige Kosten pro Strich angeben.';
|
||||||
|
} elseif ($unitPriceCents > STRICHE_MAX_PREIS_CENTS) {
|
||||||
|
$validierungsFehler[] = 'Die Kosten pro Strich überschreiten die Plausibilitätsgrenze von 100,00 €. Bitte prüfen.';
|
||||||
|
}
|
||||||
|
|
||||||
// Teilnehmer des Mandanten - dient der Zugehoerigkeitspruefung, damit ueber
|
// Teilnehmer des Mandanten - dient der Zugehoerigkeitspruefung, damit ueber
|
||||||
// das Formular keine fremden IDs bebucht werden koennen.
|
// das Formular keine fremden IDs bebucht werden koennen.
|
||||||
$eigeneTeilnehmer = [];
|
$eigeneTeilnehmer = [];
|
||||||
$stmt = $pdo->prepare('SELECT id FROM participants WHERE tenant_id = ?');
|
$stmt = $pdo->prepare('SELECT id, display_name FROM participants WHERE tenant_id = ?');
|
||||||
$stmt->execute([$tenantId]);
|
$stmt->execute([$tenantId]);
|
||||||
foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $id) {
|
foreach ($stmt->fetchAll() as $row) {
|
||||||
$eigeneTeilnehmer[(int)$id] = true;
|
$eigeneTeilnehmer[(int)$row['id']] = (string)$row['display_name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
$zuBuchen = [];
|
||||||
$pdo->beginTransaction();
|
foreach ($_POST["anzahlStriche"] ?? [] as $participantId => $anzahlStriche) {
|
||||||
|
$participantId = (int)$participantId;
|
||||||
|
if ($participantId <= 0 || !array_key_exists($participantId, $eigeneTeilnehmer)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($_POST["anzahlStriche"] ?? [] as $participantId => $anzahlStriche) {
|
$rohStriche = trim((string)$anzahlStriche);
|
||||||
$participantId = (int)$participantId;
|
$eingaben[$participantId] = $rohStriche;
|
||||||
$anzahlStriche = (int)$anzahlStriche;
|
if ($rohStriche === '' || $rohStriche === '0') {
|
||||||
if ($participantId <= 0 || $anzahlStriche === 0 || !isset($eigeneTeilnehmer[$participantId])) {
|
continue;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
|
$name = $eigeneTeilnehmer[$participantId];
|
||||||
|
if (!preg_match('/^[0-9]+$/', $rohStriche)) {
|
||||||
|
$validierungsFehler[] = sprintf('%s: Bitte eine ganze positive Strich-Anzahl eintragen.', $name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$anzahlStriche = (int)$rohStriche;
|
||||||
|
if ($anzahlStriche > STRICHE_MAX_PRO_PERSON) {
|
||||||
|
$validierungsFehler[] = sprintf(
|
||||||
|
'%s: %d Striche überschreiten die Plausibilitätsgrenze von %d pro Buchung.',
|
||||||
|
$name,
|
||||||
|
$anzahlStriche,
|
||||||
|
STRICHE_MAX_PRO_PERSON
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$zuBuchen[$participantId] = $anzahlStriche;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($validierungsFehler === [] && $zuBuchen === []) {
|
||||||
|
$validierungsFehler[] = 'Bitte mindestens eine Strich-Anzahl eintragen.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($validierungsFehler === []) {
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
foreach ($zuBuchen as $participantId => $anzahlStriche) {
|
||||||
|
ledger_record_consumption($pdo, $tenantId, $participantId, $anzahlStriche, (int)$unitPriceCents, 'manual_bulk');
|
||||||
|
$eingetragen++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ledger_record_consumption($pdo, $tenantId, $participantId, $anzahlStriche, $unitPriceCents, 'manual_bulk');
|
$pdo->commit();
|
||||||
$eingetragen++;
|
$_SESSION['flash_stricheintragen'] = ['type' => 'success', 'count' => $eingetragen];
|
||||||
|
header('Location: stricheintragen.php?action=' . urlencode($action));
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
$fehlgeschlagen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
if ($pdo->inTransaction()) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
$fehlgeschlagen = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$action = 'alle';
|
|
||||||
} else {
|
|
||||||
$action = (string)($_GET['action'] ?? 'alle');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$mitarbeiter = stricheintragen_fetch_participants($pdo, $tenantId, $action);
|
$mitarbeiter = stricheintragen_fetch_participants($pdo, $tenantId, $action);
|
||||||
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
|
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
|
||||||
$kostenproStrichAnzeige = number_format(($settings['mark_price_cents'] ?? 20) / 100, 2, '.', '');
|
$kostenproStrichAnzeige = $kostenproStrichEingabe !== null
|
||||||
|
? $kostenproStrichEingabe
|
||||||
|
: number_format(($settings['mark_price_cents'] ?? 20) / 100, 2, '.', '');
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h2>Anzahl der Striche für alle Mitarbeiter</h2>
|
<h2>Anzahl der Striche für alle Mitarbeiter</h2>
|
||||||
|
|
||||||
<?php if ($hatGespeichert): ?>
|
<?php if ($hatGespeichert): ?>
|
||||||
<?php if ($fehlgeschlagen): ?>
|
<?php if ($validierungsFehler !== []): ?>
|
||||||
|
<div class="hint-box error">
|
||||||
|
<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 Einträge konnten nicht gespeichert werden.</p></div>
|
<div class="hint-box error"><p>Die Einträge 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; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if ($erfolgsmeldung !== null): ?>
|
||||||
|
<div class="hint-box success"><p><?php echo saas_html($erfolgsmeldung); ?></p></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<ul class="actions">
|
<ul class="actions">
|
||||||
<li>
|
<li>
|
||||||
@@ -139,11 +214,12 @@ $kostenproStrichAnzeige = number_format(($settings['mark_price_cents'] ?? 20) /
|
|||||||
|
|
||||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||||
<?php echo app_csrf_field(); ?>
|
<?php echo app_csrf_field(); ?>
|
||||||
|
<input type="hidden" name="liste_action" value="<?php echo saas_html($action); ?>">
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
// Kein <br> noetig - das Feld ist ein Blockelement mit eigenem Abstand.
|
// Kein <br> noetig - das Feld ist ein Blockelement mit eigenem Abstand.
|
||||||
echo "<label for='kostenproStrich'>Kosten pro Strich:</label>
|
echo "<label for='kostenproStrich'>Kosten pro Strich:</label>
|
||||||
<input type='number' name='kostenproStrich' step='0.01' value='" . saas_html($kostenproStrichAnzeige) . "'>";
|
<input type='number' name='kostenproStrich' step='0.01' min='0.01' value='" . saas_html($kostenproStrichAnzeige) . "'>";
|
||||||
|
|
||||||
echo "<table>";
|
echo "<table>";
|
||||||
echo " <tr>
|
echo " <tr>
|
||||||
@@ -156,7 +232,7 @@ $kostenproStrichAnzeige = number_format(($settings['mark_price_cents'] ?? 20) /
|
|||||||
$name = saas_html($teilnehmer['display_name']);
|
$name = saas_html($teilnehmer['display_name']);
|
||||||
echo "<tr>";
|
echo "<tr>";
|
||||||
echo "<td><label for='anzahlStriche[$participantId]'>$name:</label></td>";
|
echo "<td><label for='anzahlStriche[$participantId]'>$name:</label></td>";
|
||||||
echo "<td><input type='number' name='anzahlStriche[$participantId]' ></td>";
|
echo "<td><input type='number' name='anzahlStriche[$participantId]' min='0' max='" . STRICHE_MAX_PRO_PERSON . "' step='1' value='" . saas_html($eingaben[$participantId] ?? '') . "'></td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
}
|
}
|
||||||
echo "</table>";
|
echo "</table>";
|
||||||
|
|||||||
Reference in New Issue
Block a user