2025-09-19 12:38:48 +02:00
2025-10-29 20:30:27 +01:00
2025-09-08 19:10:50 +02:00
2025-09-09 22:46:43 +02:00
2025-09-06 16:46:28 +02:00
2025-09-08 19:10:50 +02:00
2025-09-06 20:18:56 +02:00
2025-09-06 20:21:34 +02:00
2025-09-06 20:18:56 +02:00
2025-10-15 22:16:38 +02:00
2025-09-10 14:00:59 +02:00

bodycomfortphysio.de

Leichtgewichtiges PHP-Backend für Terminbuchungen (Slots, Buchungen, Mails mit ICS) auf einem Pixelarity-Frontend.
Adminbereich mit Bootstrap 5.3.8, Login & CSRF, Slot-Generator (inkl. Urlaub/Exceptions), Physio-Workflows.


Inhalt


Projektstruktur

build/
├─ index.php                  # Website (Kontaktformular mit Turnstile)
├─ book.php                   # Buchung/Bestätigung (Pixelarity .intro--compact)
├─ includes/
│  ├─ config.php              # ENV-Switch, cfg(), db()
|  ├─ booking_mail.php        # send_booking_mail() (HTML+Plain, ICS bei confirmed)
│  ├─ mailer.php              # PHPMailer-Factory (SMTP, UTF-8, Reply-To)
|  ├─ slots.php               # Slot-Übersicht
│  ├─ ics.php                 # .ics-Generator
│  ├─ auth.php                # Admin-Login (Sessions), CSRF-Token
├─ admin/
│  ├─ index.php               # Dashboard (Filter, Tabellen, Aktionen)
│  ├─ login.php               # Login-Formular (Bootstrap)
│  ├─ slots_tools.php         # Slots generieren/sperren + Physio ad-hoc + Mobility + Einzel-Slot
│  ├─ _head_assets.php        # Bootstrap/Icons/CSS/JS
│  ├─ _footer.php             # Footer (davidt.cloud Logo, Hilfelink)
├─ cli/
│  ├─ generate_slots.php      # ursprünglicher Generator (Logik auch im Admin)
│  ├─ test_mail.php           # Mail-Test inkl. SMTP-Debug & ICS
└─ source/                    # Frontend-Quellen (.kit/.scss), via Gulp → build/

Schnellstart

  1. DB anlegen und Schema importieren (siehe unten).
  2. .env befüllen.
  3. Webserver-Docroot auf build/ zeigen lassen.
  4. Admin-User in staff_users anlegen (siehe DB-Abschnitt).
  5. Slots nach Bedarf im Admin → „Slots generieren“ erzeugen.

Konfiguration (.env / cfg())

Alle Variablen werden über includes/config.php geladen (cfg()-Helper).
Prod wird automatisch aktiviert, wenn APP_ENV=prod oder Domain *.bodycomfortphysio.de.

Beispiel .env:

# Umgebung
APP_ENV=dev   # dev|prod

# DB DEV
DB_HOST_DEV=127.0.0.1
DB_PORT_DEV=3306
DB_NAME_DEV=bodycomfort
DB_USER_DEV=appuser
DB_PASS_DEV=change_me_app

# DB PROD
DB_HOST_PROD=127.0.0.1
DB_PORT_PROD=3306
DB_NAME_PROD=bodycomfort
DB_USER_PROD=appuser
DB_PASS_PROD=***secure***

# Mail
SMTP_HOST=mail.davidt.cloud
SMTP_PORT=465
SMTP_USER=noreply@bodycomfortphysio.de
SMTP_PASS=***secure***
FROM_ADDRESS=noreply@bodycomfortphysio.de
TO_ADDRESS_DEV=info@davidt.de
TO_ADDRESS_PROD=praxis@bodycomfortphysio.de

# Cloudflare Turnstile
TURNSTILE_SITE_KEY=0x4A...
TURNSTILE_SECRET=0x4A...

Verwendung im Code:
cfg('mail.from_address'), cfg('mail.to_address'), cfg('captcha.secret'), cfg('env') usw.


Datenbank

Schema-Überblick

Tabellen (Auszug):

  • services: id, name, code, duration_min, requires_approval, paid_online, price_cents, price_text
  • schedules: id, service_id, weekday (0=So..6=Sa), start_time, end_time, slot_every_min, buffer_min
  • slots: id, service_id, start_ts, end_ts, capacity, status('open'|'booked'|'blocked')
  • bookings: id, slot_id, customer_*, status('pending'|'confirmed'|'cancelled'), payment_status, ics_uid
  • exceptions: Urlaub/Fortbildung je Datum (+optional service_id)
  • payments: Provider-Webhooks/Belege (optional)
  • staff_users: Admin-Login (email, password_hash, role('admin'|'assistant'))
  • zoom_meetings: optional für Online-Termine (Mobility)

Admin-User anlegen (Beispiel):

INSERT INTO staff_users (name, email, password_hash, role)
VALUES (
  'Admin',
  'admin@example.com',
  -- Hash per PHP: password_hash('deinpasswort', PASSWORD_DEFAULT)
  '$2y$10$REPLACE_ME_HASHED',
  'admin'
);

Services & Schedules (Beispiele)

Physio

  • code='physio', duration_min=60, requires_approval=1, paid_online=0, price_text='165 € · ca. 60 Minuten'
  • Kein Wochenplan wird ad-hoc im Admin erzeugt („Physio-Slots an Tag X“).

Massage (45 min + 15 min Abstand → Start stündlich, aber nur zu bestimmten Zeiten)

SET @massage := (SELECT id FROM services WHERE code='massage');
DELETE FROM schedules WHERE service_id=@massage;

-- Mo (1): 13/14/15/17/18/19
INSERT INTO schedules (service_id, weekday, start_time, end_time, slot_every_min, buffer_min) VALUES
(@massage,1,'13:00:00','13:45:00',60,15),
(@massage,1,'14:00:00','14:45:00',60,15),
(@massage,1,'15:00:00','15:45:00',60,15),
(@massage,1,'17:00:00','17:45:00',60,15),
(@massage,1,'18:00:00','18:45:00',60,15),
(@massage,1,'19:00:00','19:45:00',60,15),
-- Di (2): 10/11/12/14/15
(@massage,2,'10:00:00','10:45:00',60,15),
(@massage,2,'11:00:00','11:45:00',60,15),
(@massage,2,'12:00:00','12:45:00',60,15),
(@massage,2,'14:00:00','14:45:00',60,15),
(@massage,2,'15:00:00','15:45:00',60,15),
-- Mi (3): 10/11/12/14/15
(@massage,3,'10:00:00','10:45:00',60,15),
(@massage,3,'11:00:00','11:45:00',60,15),
(@massage,3,'12:00:00','12:45:00',60,15),
(@massage,3,'14:00:00','14:45:00',60,15),
(@massage,3,'15:00:00','15:45:00',60,15),
-- Do (4): 13/14/15/17/18/19
(@massage,4,'13:00:00','13:45:00',60,15),
(@massage,4,'14:00:00','14:45:00',60,15),
(@massage,4,'15:00:00','15:45:00',60,15),
(@massage,4,'17:00:00','17:45:00',60,15),
(@massage,4,'18:00:00','18:45:00',60,15),
(@massage,4,'19:00:00','19:45:00',60,15),
-- Fr (5): 10/11/12/14/15/16
(@massage,5,'10:00:00','10:45:00',60,15),
(@massage,5,'11:00:00','11:45:00',60,15),
(@massage,5,'12:00:00','12:45:00',60,15),
(@massage,5,'14:00:00','14:45:00',60,15),
(@massage,5,'15:00:00','15:45:00',60,15),
(@massage,5,'16:00:00','16:45:00',60,15);

Lymphdrainage (MoFr 1020 Uhr, 45 min + 15 min Abstand → Start stündlich 1019)

SET @lymph := (SELECT id FROM services WHERE code='lymph');
DELETE FROM schedules WHERE service_id=@lymph;

INSERT INTO schedules (service_id, weekday, start_time, end_time, slot_every_min, buffer_min) VALUES
(@lymph,1,'10:00:00','20:00:00',60,15),
(@lymph,2,'10:00:00','20:00:00',60,15),
(@lymph,3,'10:00:00','20:00:00',60,15),
(@lymph,4,'10:00:00','20:00:00',60,15),
(@lymph,5,'10:00:00','20:00:00',60,15);
-- Wenn 20:00 Start auch erlaubt sein soll: end_time auf '20:45:00' erhöhen

Mobility (Online) (Do 18:30, Kapazität 15)

SET @mobility := (SELECT id FROM services WHERE code='mobility');
DELETE FROM schedules WHERE service_id=@mobility;

INSERT INTO schedules (service_id, weekday, start_time, end_time, slot_every_min, buffer_min) VALUES
(@mobility,4,'18:30:00','19:30:00',60,0);

Capacity-Regel: automatisch 15 bei code='mobility', sonst 1.


Adminbereich

Pfad: /admin/

Login & Sicherheit

  • Session-Login via includes/auth.php (Tabelle staff_users)
  • CSRF-Token für alle POST-Formulare
  • In DEV können bestimmte Prüfungen (z. B. Turnstile) gelockert sein (cfg('env') !== 'prod')

Funktionen & Flows

  • Filter & Quickchips (Heute/Morgen/7 Tage/30 Tage/Alle)
    Status-Labels auf Deutsch: frei, gebucht, storniert, geblockt.
  • Buchungen
    • Zu bestätigen (Physio pending) Bestätigen mit Mail + ICS
    • Stornieren mit Freitext-Begründung (Mail an Kund:in)
    • Mail erneut senden (korrekt je nach Status confirmed/pending)
    • Buchung bearbeiten (Name, E-Mail, Telefon)
    • Reaktivieren (storniert → offen/gebucht, je Workflow)
  • Slots
    • Regelmäßig anlegen bis Zieldatum (Massage, Lymph Plan aus schedules)
    • Zeitraum sperren (Urlaub) → setzt exceptions und markiert offene Slots als blocked; meldet Konflikte mit bestehenden pending/confirmed Buchungen
    • Physio an Tag X (ad-hoc in Steps von duration_min)
    • Mobility nach Plan (im Bereich)
    • Einzel-Slot anlegen (Service, Start, Ende, Status)
    • Exceptions-Respekt beim Generieren (keine Slots an Ausnahme-Tagen)
  • Sortierung: Slots starten ab heute, aufsteigend nach Datum/Zeit
  • Navigation: Buttons „Zur Website“, „Slots generieren“, Hilfelink im Footer

Mailversand

  • PHPMailer über includes/mailer.php (SMTP SSL:465, UTF-8, quoted-printable)
  • Booking-Mails via send_booking_mail():
    • status='confirmed'ICS-Anhang (method=REQUEST), Betreff „Bestätigung …“
    • status='pending' → Eingangsbestätigung ohne ICS
    • Massage: Handtuch-Hinweis nur bei bestätigtem Termin
    • Reply-To immer praxis@bodycomfortphysio.de
  • Kontaktformular (index.php):
    • From = noreply@…, Reply-To = Absender:in
    • Turnstile-Verify (cfg('captcha.secret')); in DEV optional relaxed

Öffentliche Seiten

  • index.php Startseite
  • book.php Buchung im Pixelarity-Template (.intro--compact), Preis-Text aus services.price_text

CLI-Tools

  • cli/test_mail.php sendet eine Test-Booking-Mail inkl. ICS. Nützlich zum SMTP-Debug.
  • cli/generate_slots.php anfänglicher Generator; die Funktionalität ist inzwischen im Admin verfügbar.

Fehlerquellen / Troubleshooting

  • „Undefined constant …“ → überall cfg('…') statt alter Konstanten (FROM_ADDRESS, TURNSTILE_SECRET, …).
  • ICS fehlt → ICS nur bei status=confirmed. Prüfe send_booking_mail() und ob isHTML(true) + multipart/mixed gesetzt ist (in Factory ok).
  • E-Mails kommen nicht an → SMTP-Zugangsdaten, DKIM/SPF, Absender-Domain, PHPMailer-Debug (SMTPDebug=3).
  • Turnstile schlägt fehlcfg('captcha.secret') prüfen; in DEV per cfg('env')!=='prod' deaktiviert.
  • Slots doppelt → DB-Constraint UNIQUE (service_id, start_ts) und INSERT IGNORE verhindern Duplikate.

Build & Deployment

  • Frontend: via patrick-gulp-core (separates Repo) → .kit/SASS nach build/ rendern.
  • Docroot: auf build/ zeigen lassen.
  • ENV: .env pflegen; Prod erkennt Domain automatisch.
  • Datenpflege: Services/Schedules in Adminer oder per SQL; Slots über Admin erzeugen.
  • Backups: DB (bookings, slots) regelmäßig sichern; Mail-Logs optional.
Description
No description provided
Readme 18 MiB
Languages
PHP 44.9%
Kit 26.7%
SCSS 21.6%
JavaScript 6.8%