Bemerkung und Abzuege bei Einzahlungen
Einzahlungen koennen jetzt eine Bemerkung tragen und negativ sein, damit Abzuege (Auszahlung bei Austritt, Erstattung) nachvollziehbar gebucht werden koennen. - ledger_entries.note wurde bisher nur beim Import befuellt und wird nun auch bei Einzahlungen geschrieben und angezeigt - Negative Betraege verlangen zwingend eine Bemerkung, sonst ist spaeter nicht mehr nachvollziehbar, warum jemandem Geld abgezogen wurde - Plausibilitaetsgrenze von 1000 EUR gegen Groessenordnungs-Tippfehler; bei einem Fehler in einer Zeile wird gar nichts gebucht und die Eingaben bleiben stehen - Legacy-Tabelle kl_Einzahlungen bekommt eine Bemerkung-Spalte, sonst haette ledger_mirror_legacy_payment die Notiz beim Re-Sync wieder mit NULL ueberschrieben - PayPal-Buchungen tragen jetzt Zahler, Datum und Mitteilung als Bemerkung Ausserdem behoben: letzteneintraege.php hat Einzahlungen und Striche direkt aus den Legacy-Tabellen gelesen, ohne nach Mandant zu filtern. Jeder Mandanten-Admin sah damit die Buchungen aus den Legacy-Tabellen, und das Stornieren lief ueber die nicht mandantengetrennten Legacy-IDs. Die Seite arbeitet jetzt auf dem mandantengebundenen Journal, und ledger_void_entry prueft Mandant und Buchungsart, bevor es storniert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+100
-5
@@ -41,6 +41,35 @@ function ledger_current_year(): int
|
|||||||
return (int)date('Y');
|
return (int)date('Y');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bemerkung fuer einen Journaleintrag saeubern: leere Angaben werden zu NULL,
|
||||||
|
* die Laenge auf die Spaltenbreite (1000) begrenzt. Zeilenumbrueche werden zu
|
||||||
|
* Leerzeichen, damit die Notiz in Tabellen und im PDF einzeilig bleibt.
|
||||||
|
*/
|
||||||
|
function ledger_normalize_note(?string $note): ?string
|
||||||
|
{
|
||||||
|
if ($note === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$note = preg_replace('/\s+/u', ' ', $note) ?? $note;
|
||||||
|
$note = trim($note);
|
||||||
|
if ($note === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (strlen($note) > 1000) {
|
||||||
|
// Byteweise kuerzen und danach eine ggf. angeschnittene Multibyte-
|
||||||
|
// Sequenz am Ende wegnehmen (preg_match mit /u schlaegt bei
|
||||||
|
// ungueltigem UTF-8 fehl). 1000 Bytes sind hoechstens 1000 Zeichen,
|
||||||
|
// passen also immer in die Spalte.
|
||||||
|
$note = substr($note, 0, 1000);
|
||||||
|
while ($note !== '' && preg_match('//u', $note) !== 1) {
|
||||||
|
$note = substr($note, 0, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $note;
|
||||||
|
}
|
||||||
|
|
||||||
function ledger_default_tenant_slug(): string
|
function ledger_default_tenant_slug(): string
|
||||||
{
|
{
|
||||||
$tenantSlug = app_env('M4_DEFAULT_TENANT_SLUG');
|
$tenantSlug = app_env('M4_DEFAULT_TENANT_SLUG');
|
||||||
@@ -568,7 +597,7 @@ function ledger_mirror_legacy_payment(PDO $pdo, int $tenantId, int $legacyPaymen
|
|||||||
NULL,
|
NULL,
|
||||||
e.Datum,
|
e.Datum,
|
||||||
'legacy_payment',
|
'legacy_payment',
|
||||||
NULL,
|
e.Bemerkung,
|
||||||
'kl_Einzahlungen',
|
'kl_Einzahlungen',
|
||||||
e.EinzahlungsID
|
e.EinzahlungsID
|
||||||
FROM kl_Einzahlungen e
|
FROM kl_Einzahlungen e
|
||||||
@@ -606,6 +635,66 @@ function ledger_mirror_legacy_payment(PDO $pdo, int $tenantId, int $legacyPaymen
|
|||||||
* deleting it, so corrections stay auditable. Idempotent: voiding an
|
* deleting it, so corrections stay auditable. Idempotent: voiding an
|
||||||
* already-voided or missing entry is a no-op and returns false.
|
* already-voided or missing entry is a no-op and returns false.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Storniert einen Journaleintrag anhand seiner Ledger-ID - immer auf den
|
||||||
|
* Mandanten eingegrenzt, damit ueber eine untergeschobene ID nicht in fremden
|
||||||
|
* Mandanten gebucht werden kann. Haengt am Eintrag noch eine Legacy-Zeile,
|
||||||
|
* wird sie mitgeloescht, damit der Mirror den Eintrag nicht wieder auferstehen
|
||||||
|
* laesst.
|
||||||
|
*
|
||||||
|
* @param string|null $expectedType Absichern, dass z. B. ueber das
|
||||||
|
* Einzahlungs-Formular kein Strich storniert
|
||||||
|
* werden kann.
|
||||||
|
*/
|
||||||
|
function ledger_void_entry(PDO $pdo, int $tenantId, int $entryId, ?string $expectedType = null): bool
|
||||||
|
{
|
||||||
|
// Nur bekannte Legacy-Tabellen, damit der Tabellenname nie ungeprueft in
|
||||||
|
// ein DELETE wandert.
|
||||||
|
$legacyKeys = [
|
||||||
|
'kl_Einzahlungen' => 'EinzahlungsID',
|
||||||
|
'kl_Kaffeeverbrauch' => 'VerbrauchID',
|
||||||
|
];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT id, type, legacy_table, legacy_id
|
||||||
|
FROM ledger_entries
|
||||||
|
WHERE id = ? AND tenant_id = ? AND voided_at IS NULL
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([$entryId, $tenantId]);
|
||||||
|
$entry = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($entry === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($expectedType !== null && (string)$entry['type'] !== $expectedType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$pdo->prepare('UPDATE ledger_entries SET voided_at = NOW() WHERE id = ? AND tenant_id = ? AND voided_at IS NULL')
|
||||||
|
->execute([$entryId, $tenantId]);
|
||||||
|
|
||||||
|
$legacyTable = $entry['legacy_table'] !== null ? (string)$entry['legacy_table'] : null;
|
||||||
|
if ($legacyTable !== null && $entry['legacy_id'] !== null && isset($legacyKeys[$legacyTable])) {
|
||||||
|
$pdo->prepare("DELETE FROM {$legacyTable} WHERE {$legacyKeys[$legacyTable]} = ?")
|
||||||
|
->execute([(int)$entry['legacy_id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function ledger_void_entry_by_legacy_id(PDO $pdo, int $tenantId, string $legacyTable, int $legacyId): bool
|
function ledger_void_entry_by_legacy_id(PDO $pdo, int $tenantId, string $legacyTable, int $legacyId): bool
|
||||||
{
|
{
|
||||||
$stmt = $pdo->prepare(
|
$stmt = $pdo->prepare(
|
||||||
@@ -702,16 +791,22 @@ function ledger_record_consumption(PDO $pdo, int $tenantId, int $participantId,
|
|||||||
* Records a payment entry straight into the ledger, without any legacy
|
* Records a payment entry straight into the ledger, without any legacy
|
||||||
* kl_Einzahlungen row. Used for tenants that have no legacy shadow table.
|
* kl_Einzahlungen row. Used for tenants that have no legacy shadow table.
|
||||||
*/
|
*/
|
||||||
function ledger_record_payment(PDO $pdo, int $tenantId, int $participantId, int $amountCents, string $source, ?int $createdByUserId = null): int
|
/**
|
||||||
|
* Bucht eine Einzahlung. $amountCents darf negativ sein (Abzug, Auszahlung,
|
||||||
|
* Korrektur) - fuer Tippfehler ist aber ledger_void_entry der richtige Weg,
|
||||||
|
* damit die Auswertung nicht durch Gegenbuchungen aufgeblaeht wird.
|
||||||
|
*/
|
||||||
|
function ledger_record_payment(PDO $pdo, int $tenantId, int $participantId, int $amountCents, string $source, ?int $createdByUserId = null, ?string $note = null): int
|
||||||
{
|
{
|
||||||
|
$note = ledger_normalize_note($note);
|
||||||
$stmt = $pdo->prepare(
|
$stmt = $pdo->prepare(
|
||||||
"INSERT INTO ledger_entries
|
"INSERT INTO ledger_entries
|
||||||
(tenant_id, participant_id, type, amount_cents, booked_at, source, created_by_user_id)
|
(tenant_id, participant_id, type, amount_cents, booked_at, source, created_by_user_id, note)
|
||||||
SELECT ?, id, 'payment', ?, NOW(), ?, ?
|
SELECT ?, id, 'payment', ?, NOW(), ?, ?, ?
|
||||||
FROM participants
|
FROM participants
|
||||||
WHERE id = ? AND tenant_id = ?"
|
WHERE id = ? AND tenant_id = ?"
|
||||||
);
|
);
|
||||||
$stmt->execute([$tenantId, $amountCents, $source, $createdByUserId, $participantId, $tenantId]);
|
$stmt->execute([$tenantId, $amountCents, $source, $createdByUserId, $note, $participantId, $tenantId]);
|
||||||
|
|
||||||
if ($stmt->rowCount() !== 1) {
|
if ($stmt->rowCount() !== 1) {
|
||||||
throw new RuntimeException('Die Einzahlung konnte nicht gespeichert werden.');
|
throw new RuntimeException('Die Einzahlung konnte nicht gespeichert werden.');
|
||||||
|
|||||||
+29
-7
@@ -100,15 +100,15 @@ function paypal_inbox_extract_token(string $address): ?string
|
|||||||
*
|
*
|
||||||
* @return int Ledger-Entry-ID
|
* @return int Ledger-Entry-ID
|
||||||
*/
|
*/
|
||||||
function paypal_book_payment(PDO $pdo, int $tenantId, array $participant, int $netCents, ?int $actorUserId): int
|
function paypal_book_payment(PDO $pdo, int $tenantId, array $participant, int $netCents, ?int $actorUserId, ?string $note = null): int
|
||||||
{
|
{
|
||||||
$legacyMitarbeiterId = isset($participant['legacy_mitarbeiter_id']) && $participant['legacy_mitarbeiter_id'] !== null
|
$legacyMitarbeiterId = isset($participant['legacy_mitarbeiter_id']) && $participant['legacy_mitarbeiter_id'] !== null
|
||||||
? (int) $participant['legacy_mitarbeiter_id']
|
? (int) $participant['legacy_mitarbeiter_id']
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
if ($legacyMitarbeiterId > 0) {
|
if ($legacyMitarbeiterId > 0) {
|
||||||
$stmt = $pdo->prepare('INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum) VALUES (?, ?, ?)');
|
$stmt = $pdo->prepare('INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Bemerkung, Datum) VALUES (?, ?, ?, ?)');
|
||||||
$stmt->execute([$legacyMitarbeiterId, $netCents / 100, date('Y-m-d H:i:s')]);
|
$stmt->execute([$legacyMitarbeiterId, $netCents / 100, $note, date('Y-m-d H:i:s')]);
|
||||||
$legacyPaymentId = (int) $pdo->lastInsertId();
|
$legacyPaymentId = (int) $pdo->lastInsertId();
|
||||||
ledger_mirror_legacy_payment($pdo, $tenantId, $legacyPaymentId);
|
ledger_mirror_legacy_payment($pdo, $tenantId, $legacyPaymentId);
|
||||||
|
|
||||||
@@ -120,7 +120,25 @@ function paypal_book_payment(PDO $pdo, int $tenantId, array $participant, int $n
|
|||||||
return (int) $idStmt->fetchColumn();
|
return (int) $idStmt->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ledger_record_payment($pdo, $tenantId, (int) $participant['participant_id'], $netCents, 'paypal_import', $actorUserId);
|
return ledger_record_payment($pdo, $tenantId, (int) $participant['participant_id'], $netCents, 'paypal_import', $actorUserId, $note);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bemerkung fuer eine aus PayPal uebernommene Buchung: Zahler und Datum, damit
|
||||||
|
* im Journal nachvollziehbar bleibt, woher die Gutschrift stammt.
|
||||||
|
*/
|
||||||
|
function paypal_booking_note(array $payment): string
|
||||||
|
{
|
||||||
|
$note = 'PayPal: ' . trim((string) ($payment['payer_name'] ?? ''));
|
||||||
|
if (!empty($payment['paid_at'])) {
|
||||||
|
$note .= ' vom ' . date('d.m.Y', strtotime((string) $payment['paid_at']));
|
||||||
|
}
|
||||||
|
$mitteilung = trim((string) ($payment['note'] ?? ''));
|
||||||
|
if ($mitteilung !== '') {
|
||||||
|
$note .= ' ("' . $mitteilung . '")';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $note;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,7 +211,11 @@ function paypal_reconcile(PDO $pdo, int $tenantId, array $parsed, ?int $actorUse
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
$ledgerId = paypal_book_payment($pdo, $tenantId, $participant, $netCents, $actorUserId);
|
$ledgerId = paypal_book_payment($pdo, $tenantId, $participant, $netCents, $actorUserId, paypal_booking_note([
|
||||||
|
'payer_name' => $parsed['payer_name'] ?? '',
|
||||||
|
'paid_at' => $parsed['date'] ?? null,
|
||||||
|
'note' => $parsed['note'] ?? null,
|
||||||
|
]));
|
||||||
$pdo->prepare("UPDATE paypal_payments SET participant_id = ?, ledger_entry_id = ?, status = 'booked' WHERE id = ?")
|
$pdo->prepare("UPDATE paypal_payments SET participant_id = ?, ledger_entry_id = ?, status = 'booked' WHERE id = ?")
|
||||||
->execute([(int) $participant['participant_id'], $ledgerId, $paymentId]);
|
->execute([(int) $participant['participant_id'], $ledgerId, $paymentId]);
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
@@ -273,7 +295,7 @@ function paypal_fetch_unmatched(PDO $pdo, int $tenantId): array
|
|||||||
*/
|
*/
|
||||||
function paypal_assign_payment(PDO $pdo, int $tenantId, int $paymentId, int $participantId, ?int $actorUserId): array
|
function paypal_assign_payment(PDO $pdo, int $tenantId, int $paymentId, int $participantId, ?int $actorUserId): array
|
||||||
{
|
{
|
||||||
$stmt = $pdo->prepare("SELECT id, net_cents, status FROM paypal_payments WHERE id = ? AND tenant_id = ?");
|
$stmt = $pdo->prepare("SELECT id, net_cents, status, payer_name, note, paid_at FROM paypal_payments WHERE id = ? AND tenant_id = ?");
|
||||||
$stmt->execute([$paymentId, $tenantId]);
|
$stmt->execute([$paymentId, $tenantId]);
|
||||||
$payment = $stmt->fetch();
|
$payment = $stmt->fetch();
|
||||||
if ($payment === false || (string) $payment['status'] !== 'unmatched') {
|
if ($payment === false || (string) $payment['status'] !== 'unmatched') {
|
||||||
@@ -288,7 +310,7 @@ function paypal_assign_payment(PDO $pdo, int $tenantId, int $paymentId, int $par
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
$ledgerId = paypal_book_payment($pdo, $tenantId, $participant, (int) $payment['net_cents'], $actorUserId);
|
$ledgerId = paypal_book_payment($pdo, $tenantId, $participant, (int) $payment['net_cents'], $actorUserId, paypal_booking_note($payment));
|
||||||
$pdo->prepare("UPDATE paypal_payments SET participant_id = ?, ledger_entry_id = ?, status = 'booked' WHERE id = ?")
|
$pdo->prepare("UPDATE paypal_payments SET participant_id = ?, ledger_entry_id = ?, status = 'booked' WHERE id = ?")
|
||||||
->execute([$participantId, $ledgerId, $paymentId]);
|
->execute([$participantId, $ledgerId, $paymentId]);
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
-- Bemerkung/Titel fuer Einzahlungen.
|
||||||
|
--
|
||||||
|
-- ledger_entries.note existiert bereits (siehe 0006), wurde bei Einzahlungen
|
||||||
|
-- aber nie befuellt. Beim Default-Mandanten laeuft die Buchung als Dual-Write
|
||||||
|
-- ueber die Legacy-Tabelle kl_Einzahlungen, und ledger_mirror_legacy_payment
|
||||||
|
-- ueberschreibt note beim Re-Sync mit dem Legacy-Wert. Ohne eine Spalte auf
|
||||||
|
-- der Legacy-Seite wuerde die Bemerkung dort also stillschweigend wieder
|
||||||
|
-- verschwinden.
|
||||||
|
ALTER TABLE kl_Einzahlungen
|
||||||
|
ADD COLUMN Bemerkung VARCHAR(1000) NULL AFTER Betrag;
|
||||||
+113
-12
@@ -83,6 +83,13 @@ function einzahlung_fetch_participants(PDO $pdo, int $tenantId, string $action):
|
|||||||
$eingetragen = 0;
|
$eingetragen = 0;
|
||||||
$fehlgeschlagen = false;
|
$fehlgeschlagen = false;
|
||||||
$hatGespeichert = 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
|
// Verarbeitung des Formulars, wenn es gesendet wurde
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
||||||
@@ -100,31 +107,105 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
$legacyMap[(int)$row['id']] = $row['legacy_mitarbeiter_id'] !== null ? (int)$row['legacy_mitarbeiter_id'] : null;
|
$legacyMap[(int)$row['id']] = $row['legacy_mitarbeiter_id'] !== null ? (int)$row['legacy_mitarbeiter_id'] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Namen fuer verstaendliche Fehlermeldungen.
|
||||||
$pdo->beginTransaction();
|
$namen = [];
|
||||||
$insertLegacy = $pdo->prepare(
|
$stmtNamen = $pdo->prepare('SELECT id, display_name FROM participants WHERE tenant_id = ?');
|
||||||
"INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum) VALUES (?, ?, ?)"
|
$stmtNamen->execute([$tenantId]);
|
||||||
);
|
foreach ($stmtNamen->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) {
|
foreach ($_POST["anzahlBetrag"] ?? [] as $participantId => $anzahlBetrag) {
|
||||||
$participantId = (int)$participantId;
|
$participantId = (int)$participantId;
|
||||||
$anzahlBetrag = floatval($anzahlBetrag);
|
if ($participantId <= 0 || !array_key_exists($participantId, $legacyMap)) {
|
||||||
if ($participantId <= 0 || $anzahlBetrag == 0.0 || !array_key_exists($participantId, $legacyMap)) {
|
|
||||||
continue;
|
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();
|
||||||
|
$insertLegacy = $pdo->prepare(
|
||||||
|
"INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Bemerkung, Datum) VALUES (?, ?, ?, ?)"
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($zuBuchen as $participantId => $zeile) {
|
||||||
$legacyMitarbeiterId = $legacyMap[$participantId];
|
$legacyMitarbeiterId = $legacyMap[$participantId];
|
||||||
if ($legacyMitarbeiterId !== null) {
|
if ($legacyMitarbeiterId !== null) {
|
||||||
$insertLegacy->execute([$legacyMitarbeiterId, $anzahlBetrag, $datum]);
|
$insertLegacy->execute([$legacyMitarbeiterId, $zeile['betrag'], $zeile['bemerkung'], $datum]);
|
||||||
$legacyPaymentId = (int)$pdo->lastInsertId();
|
$legacyPaymentId = (int)$pdo->lastInsertId();
|
||||||
ledger_mirror_legacy_payment($pdo, $tenantId, $legacyPaymentId);
|
ledger_mirror_legacy_payment($pdo, $tenantId, $legacyPaymentId);
|
||||||
} else {
|
} else {
|
||||||
ledger_record_payment($pdo, $tenantId, $participantId, (int)round($anzahlBetrag * 100), 'manual_bulk');
|
ledger_record_payment(
|
||||||
|
$pdo,
|
||||||
|
$tenantId,
|
||||||
|
$participantId,
|
||||||
|
(int)round($zeile['betrag'] * 100),
|
||||||
|
'manual_bulk',
|
||||||
|
$saasUser['user_id'] ?? null,
|
||||||
|
$zeile['bemerkung']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$eingetragen++;
|
$eingetragen++;
|
||||||
}
|
}
|
||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
$eingaben = []; // Erfolgreich gebucht: Formular wieder leeren.
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if ($pdo->inTransaction()) {
|
if ($pdo->inTransaction()) {
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
@@ -133,6 +214,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$action = 'alle';
|
$action = 'alle';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$action = (string)($_GET['action'] ?? 'alle');
|
$action = (string)($_GET['action'] ?? 'alle');
|
||||||
}
|
}
|
||||||
@@ -144,13 +226,27 @@ $mitarbeiter = einzahlung_fetch_participants($pdo, $tenantId, $action);
|
|||||||
<h2>Einzahlungen für alle Mitarbeiter</h2>
|
<h2>Einzahlungen für alle Mitarbeiter</h2>
|
||||||
|
|
||||||
<?php if ($hatGespeichert): ?>
|
<?php if ($hatGespeichert): ?>
|
||||||
<?php if ($fehlgeschlagen): ?>
|
<?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>
|
<div class="hint-box error"><p>Die Einzahlungen konnten nicht gespeichert werden.</p></div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="hint-box success"><p><?php echo (int)$eingetragen; ?> Einträge erfolgreich hinzugefügt.</p></div>
|
<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; ?>
|
||||||
|
|
||||||
|
<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">
|
<ul class="actions">
|
||||||
<li>
|
<li>
|
||||||
<form action="einzahlung.php" method="get">
|
<form action="einzahlung.php" method="get">
|
||||||
@@ -189,13 +285,18 @@ $mitarbeiter = einzahlung_fetch_participants($pdo, $tenantId, $action);
|
|||||||
echo " <tr>
|
echo " <tr>
|
||||||
<th>Mitarbeiter</th>
|
<th>Mitarbeiter</th>
|
||||||
<th>Betrag</th>
|
<th>Betrag</th>
|
||||||
|
<th>Bemerkung</th>
|
||||||
</tr>";
|
</tr>";
|
||||||
foreach ($mitarbeiter as $teilnehmer) {
|
foreach ($mitarbeiter as $teilnehmer) {
|
||||||
$participantId = $teilnehmer['participant_id'];
|
$participantId = (int)$teilnehmer['participant_id'];
|
||||||
$name = saas_html($teilnehmer['display_name']);
|
$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 "<tr>";
|
||||||
echo "<td><label for='anzahlBetrag[$participantId]'>$name </label></td>";
|
echo "<td><label for='anzahlBetrag[$participantId]'>$name </label></td>";
|
||||||
echo "<td><input type='number' name='anzahlBetrag[$participantId]' step='0.01'></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 "</tr>";
|
||||||
}
|
}
|
||||||
echo "</table>";
|
echo "</table>";
|
||||||
|
|||||||
+46
-126
@@ -37,153 +37,80 @@ if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadres
|
|||||||
|
|
||||||
if($hasAccess){
|
if($hasAccess){
|
||||||
|
|
||||||
// Funktion zum Löschen einer Einzahlung: der Ledger-Eintrag wird als Storno
|
// Stornieren einer Einzahlung. Gearbeitet wird auf der Ledger-ID, weil nur die
|
||||||
// markiert statt geloescht, damit die Buchungshistorie nachvollziehbar bleibt.
|
// mandantensicher geprueft werden kann - die Legacy-IDs sind nicht nach
|
||||||
function loescheEinzahlung($einzahlungID, PDO $pdo, int $tenantId) {
|
// Mandant getrennt. Der Eintrag wird als storniert markiert statt geloescht,
|
||||||
try {
|
// damit die Buchungshistorie nachvollziehbar bleibt.
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
ledger_void_entry_by_legacy_id($pdo, $tenantId, 'kl_Einzahlungen', $einzahlungID);
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare('DELETE FROM kl_Einzahlungen WHERE EinzahlungsID = ?');
|
|
||||||
$stmt->execute([$einzahlungID]);
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
|
|
||||||
return true; // Erfolgreich gelöscht
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
if ($pdo->inTransaction()) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Die Einzahlung konnte nicht gelöscht werden.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Überprüfen, ob ein Löschvorgang angefordert wurde
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loescheneinzahlung") {
|
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loescheneinzahlung") {
|
||||||
$einzahlungID = (int)$_POST["einzahlungID"];
|
$entryId = (int)($_POST["entryId"] ?? 0);
|
||||||
|
|
||||||
$ergebnis = loescheEinzahlung($einzahlungID, $pdo, $tenantId);
|
if (ledger_void_entry($pdo, $tenantId, $entryId, 'payment')) {
|
||||||
|
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'payment.voided', 'ledger_entries', $entryId);
|
||||||
if ($ergebnis === true) {
|
echo "<div class='hint-box success'><p>Einzahlung wurde storniert.</p></div>";
|
||||||
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'payment.voided', 'kl_Einzahlungen', $einzahlungID);
|
|
||||||
echo "Einzahlung erfolgreich gelöscht.";
|
|
||||||
} else {
|
} else {
|
||||||
echo "Fehler: $ergebnis";
|
echo "<div class='hint-box error'><p>Die Einzahlung konnte nicht storniert werden. Vielleicht wurde sie bereits storniert.</p></div>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$einzahlungen = ledger_fetch_recent_entries($pdo, $tenantId, ['type' => 'payment', 'limit' => 100]);
|
||||||
|
|
||||||
|
|
||||||
// SQL-Abfrage für die letzten 100 Einzahlungen
|
|
||||||
$sqlEinzahlungen = "SELECT TOP 100 kl_Mitarbeiter.Name AS MitarbeiterName, kl_Einzahlungen.EinzahlungsID, kl_Einzahlungen.Betrag, kl_Einzahlungen.Datum
|
|
||||||
FROM kl_Einzahlungen
|
|
||||||
JOIN kl_Mitarbeiter ON kl_Einzahlungen.MitarbeiterID = kl_Mitarbeiter.MitarbeiterID
|
|
||||||
ORDER BY kl_Einzahlungen.Datum DESC";
|
|
||||||
|
|
||||||
$stmtEinzahlungen = sqlsrv_query($conn, $sqlEinzahlungen);
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Letzte 100 Einzahlungen</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<h2>Letzte 100 Einzahlungen</h2>
|
<h2>Letzte 100 Einzahlungen</h2>
|
||||||
|
|
||||||
|
<p><small>Stornieren ist der richtige Weg, wenn eine Buchung <b>falsch eingetragen</b> wurde. Für einen tatsächlichen Abzug (Auszahlung, Erstattung) stattdessen unter <a href="einzahlung.php">Einzahlung eintragen</a> einen negativen Betrag mit Bemerkung buchen.</small></p>
|
||||||
|
|
||||||
<table border="1" class="table table-striped">
|
<table border="1" class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name des Mitarbeiters</th>
|
<th>Name des Mitarbeiters</th>
|
||||||
<th>Betrag</th>
|
<th>Betrag</th>
|
||||||
|
<th>Bemerkung</th>
|
||||||
<th>Datum</th>
|
<th>Datum</th>
|
||||||
<th>Aktion</th>
|
<th>Aktion</th>
|
||||||
</tr>
|
</tr>
|
||||||
<?php
|
<?php
|
||||||
while ($row = sqlsrv_fetch_array($stmtEinzahlungen, SQLSRV_FETCH_ASSOC)) {
|
foreach ($einzahlungen as $eintrag) {
|
||||||
$einzahlungID = $row['EinzahlungsID'];
|
$betrag = number_format($eintrag['amount_cents'] / 100, 2, ',', '.');
|
||||||
$mitarbeiterName = $row['MitarbeiterName'];
|
|
||||||
$betrag = $row['Betrag'];
|
|
||||||
$datum = $row['Datum']->format('Y-m-d H:i:s'); // Das Format kann angepasst werden
|
|
||||||
$betrag = number_format($betrag, 2, ',', '.');
|
|
||||||
echo "<tr>";
|
echo "<tr>";
|
||||||
echo "<td>{$mitarbeiterName}</td>";
|
echo "<td>" . saas_html($eintrag['display_name']) . "</td>";
|
||||||
echo "<td>{$betrag}</td>";
|
echo "<td>" . saas_html($betrag) . " €</td>";
|
||||||
echo "<td>{$datum}</td>";
|
echo "<td>" . saas_html((string)($eintrag['note'] ?? '')) . "</td>";
|
||||||
|
echo "<td>" . saas_html($eintrag['booked_at']) . "</td>";
|
||||||
echo "<td>";
|
echo "<td>";
|
||||||
echo "<form method='post' action='letzteneintraege.php'>";
|
echo "<form method='post' action='letzteneintraege.php' onsubmit=\"return confirm('Diese Einzahlung wirklich stornieren?');\">";
|
||||||
echo "<input type='hidden' name='aktion' value='loescheneinzahlung'>";
|
echo "<input type='hidden' name='aktion' value='loescheneinzahlung'>";
|
||||||
echo "<input type='hidden' name='einzahlungID' value='{$einzahlungID}'>";
|
echo "<input type='hidden' name='entryId' value='" . (int)$eintrag['id'] . "'>";
|
||||||
echo app_csrf_field();
|
echo app_csrf_field();
|
||||||
echo "<button type='submit'>Löschen</button>";
|
echo "<button type='submit'>Stornieren</button>";
|
||||||
echo "</form>";
|
echo "</form>";
|
||||||
echo "</td>";
|
echo "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
}
|
}
|
||||||
|
if ($einzahlungen === []) {
|
||||||
|
echo "<tr><td colspan='5'>Noch keine Einzahlungen erfasst.</td></tr>";
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
<?php
|
<?php
|
||||||
// Funktion zum Löschen eines Strich-Eintrags: der Ledger-Eintrag wird als
|
// Stornieren eines Strich-Eintrags, ebenfalls ueber die mandantensichere
|
||||||
// Storno markiert statt geloescht, damit die Buchungshistorie nachvollziehbar bleibt.
|
// Ledger-ID.
|
||||||
function loescheStrichEintrag($strichID, PDO $pdo, int $tenantId) {
|
|
||||||
try {
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
ledger_void_entry_by_legacy_id($pdo, $tenantId, 'kl_Kaffeeverbrauch', $strichID);
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare('DELETE FROM kl_Kaffeeverbrauch WHERE VerbrauchID = ?');
|
|
||||||
$stmt->execute([$strichID]);
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
|
|
||||||
return true; // Erfolgreich gelöscht
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
if ($pdo->inTransaction()) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Der Strich-Eintrag konnte nicht gelöscht werden.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Überprüfen, ob ein Löschvorgang angefordert wurde
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loeschen") {
|
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["aktion"] == "loeschen") {
|
||||||
$strichID = (int)$_POST["strichID"];
|
$entryId = (int)($_POST["entryId"] ?? 0);
|
||||||
|
|
||||||
$ergebnis = loescheStrichEintrag($strichID, $pdo, $tenantId);
|
if (ledger_void_entry($pdo, $tenantId, $entryId, 'consumption')) {
|
||||||
|
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'consumption.voided', 'ledger_entries', $entryId);
|
||||||
if ($ergebnis === true) {
|
echo "<div class='hint-box success'><p>Strich-Eintrag wurde storniert.</p></div>";
|
||||||
app_audit_log($pdo, $tenantId, $saasUser['user_id'] ?? null, 'consumption.voided', 'kl_Kaffeeverbrauch', $strichID);
|
|
||||||
echo "Strich-Eintrag erfolgreich gelöscht.";
|
|
||||||
} else {
|
} else {
|
||||||
echo "Fehler: $ergebnis";
|
echo "<div class='hint-box error'><p>Der Strich-Eintrag konnte nicht storniert werden. Vielleicht wurde er bereits storniert.</p></div>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SQL-Abfrage für die letzten 100 Strich-Einträge
|
$striche = ledger_fetch_recent_entries($pdo, $tenantId, ['type' => 'consumption', 'limit' => 100]);
|
||||||
$sqlStriche = "SELECT TOP 100 kl_Kaffeeverbrauch.VerbrauchID, kl_Mitarbeiter.Name AS MitarbeiterName, kl_Kaffeeverbrauch.AnzahlStriche, kl_Kaffeeverbrauch.Kosten, kl_Kaffeeverbrauch.Datum
|
|
||||||
FROM kl_Kaffeeverbrauch
|
|
||||||
JOIN kl_Mitarbeiter ON kl_Kaffeeverbrauch.MitarbeiterID = kl_Mitarbeiter.MitarbeiterID
|
|
||||||
ORDER BY kl_Kaffeeverbrauch.Datum DESC";
|
|
||||||
|
|
||||||
$stmtStriche = sqlsrv_query($conn, $sqlStriche);
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Letzte 100 Strich-Einträge</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>Letzte 100 Strich-Einträge</h2>
|
<h2>Letzte 100 Strich-Einträge</h2>
|
||||||
|
|
||||||
<table border="1" class="table table-striped">
|
<table border="1" class="table table-striped">
|
||||||
@@ -195,37 +122,30 @@ $stmtStriche = sqlsrv_query($conn, $sqlStriche);
|
|||||||
<th>Aktion</th>
|
<th>Aktion</th>
|
||||||
</tr>
|
</tr>
|
||||||
<?php
|
<?php
|
||||||
while ($row = sqlsrv_fetch_array($stmtStriche, SQLSRV_FETCH_ASSOC)) {
|
foreach ($striche as $eintrag) {
|
||||||
$strichID = $row['VerbrauchID'];
|
// Verbrauch ist im Journal negativ hinterlegt, hier als positive Kosten zeigen.
|
||||||
$mitarbeiterName = $row['MitarbeiterName'];
|
$betrag = number_format(-$eintrag['amount_cents'] / 100, 2, ',', '.');
|
||||||
$anzahlStriche = $row['AnzahlStriche'];
|
|
||||||
$betrag = $row['Kosten'];
|
|
||||||
$datum = $row['Datum']->format('Y-m-d H:i:s'); // Das Format kann angepasst werden
|
|
||||||
$anzahlStriche = number_format($anzahlStriche, 0, ',', '.');
|
|
||||||
$betrag = number_format($betrag, 0, ',', '.');
|
|
||||||
echo "<tr>";
|
echo "<tr>";
|
||||||
echo "<td>{$mitarbeiterName}</td>";
|
echo "<td>" . saas_html($eintrag['display_name']) . "</td>";
|
||||||
echo "<td>{$anzahlStriche}</td>";
|
echo "<td>" . (int)($eintrag['marks_count'] ?? 0) . "</td>";
|
||||||
echo "<td>{$betrag}</td>";
|
echo "<td>" . saas_html($betrag) . " €</td>";
|
||||||
echo "<td>{$datum}</td>";
|
echo "<td>" . saas_html($eintrag['booked_at']) . "</td>";
|
||||||
echo "<td>";
|
echo "<td>";
|
||||||
echo "<form method='post' action='letzteneintraege.php'>";
|
echo "<form method='post' action='letzteneintraege.php' onsubmit=\"return confirm('Diesen Strich-Eintrag wirklich stornieren?');\">";
|
||||||
echo "<input type='hidden' name='aktion' value='loeschen'>";
|
echo "<input type='hidden' name='aktion' value='loeschen'>";
|
||||||
echo "<input type='hidden' name='strichID' value='{$strichID}'>";
|
echo "<input type='hidden' name='entryId' value='" . (int)$eintrag['id'] . "'>";
|
||||||
echo app_csrf_field();
|
echo app_csrf_field();
|
||||||
echo "<button type='submit'>Löschen</button>";
|
echo "<button type='submit'>Stornieren</button>";
|
||||||
echo "</form>";
|
echo "</form>";
|
||||||
echo "</td>";
|
echo "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
}
|
}
|
||||||
|
if ($striche === []) {
|
||||||
|
echo "<tr><td colspan='5'>Noch keine Striche erfasst.</td></tr>";
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<?php
|
<?php
|
||||||
}else{
|
}else{
|
||||||
echo "<h2>Kein Zugriff</h2>";
|
echo "<h2>Kein Zugriff</h2>";
|
||||||
|
|||||||
Reference in New Issue
Block a user