WordPress-Buchungssystem für begrenzte Ressourcen: Verfügbarkeit, Puffer, Hold und Anfrage-Fallback

Ein Buchungssystem wirkt auf den ersten Blick simpel: Zeitraum wählen, Verfügbarkeit prüfen, Preis anzeigen, buchen. Sobald aber nur sehr wenige Ressourcen vorhanden sind – etwa einzelne Parkplätze, Räume, Geräte oder Mietobjekte – entscheidet die Geschäftslogik darüber, ob das System zuverlässig arbeitet oder irgendwann doppelt verkauft.

In einem kleinen WordPress-Parkplatzprojekt habe ich die Buchungslogik deshalb bewusst in mehrere Ebenen getrennt: Nutzungszeitraum, Pufferzeit, kurzer technischer Hold, Zahlungsfrist und Anfrage-Fallback. Diese Trennung ist auf viele andere knappe Ressourcen übertragbar.

Grundidee: „Verfügbar“ ist kein einzelnes Ja/Nein-Feld. Es ist das Ergebnis aus Zeitintervall, Status, Puffer, temporären Sperren und bereits akzeptierten Anfragen.

1. Zuerst den echten Nutzungszeitraum definieren

Der vom Kunden eingegebene Termin ist nicht zwingend der Zeitraum, in dem eine Ressource tatsächlich blockiert sein muss. Im Parkplatzbeispiel wird nicht nur zwischen Abflug und Rückkehr blockiert. Der Parkplatz muss bereits zwei Stunden vor dem Abflug frei sein und bleibt bis zwei Stunden nach der Rückkehr reserviert.

block_start = abflug - 2 Stunden
block_end   = rückkehr + 2 Stunden

Erst mit diesen berechneten Werten darf die Kollisionsprüfung arbeiten. Sonst zeigt die Oberfläche einen Platz als frei an, obwohl der Kunde praktisch noch nicht weg oder bereits früher da ist.

2. Pufferzeiten gehören in die Verfügbarkeitslogik

Zusätzlich kann zwischen zwei Belegungen ein nicht verkaufbarer Puffer nötig sein. In meinem Fall ist das ein Tag zwischen zwei Belegungen. Der Puffer ist kein hübscher Hinweis im Kalender, sondern Teil der Sperrlogik.

Praktisch lässt sich das so denken:

bestehende_blockierung_bis = bestehendes_ende + puffer

neue_buchung_ist_frei =
    neuer_start >= bestehende_blockierung_bis
    ODER
    neues_ende <= bestehender_start

Bei mehreren Buchungen prüft man gegen jedes relevante Intervall. Wichtig ist dabei eine klare Festlegung, ob der Puffer nur nach einer Belegung oder symmetrisch vor und nach jeder Belegung gilt. Wer beide Seiten jeweils um einen Tag erweitert, erzeugt sonst ungewollt zwei Puffertage.

3. Kurzer Hold und Zahlungsfrist sind zwei verschiedene Dinge

Ein häufiger Architekturfehler ist, einen temporären Checkout-Hold mit der Zahlungsfrist zu vermischen.

  • Technischer Hold: verhindert für kurze Zeit, dass zwei Kunden denselben letzten Platz gleichzeitig durch den Checkout schieben. In meinem Aufbau: etwa zwei Stunden.
  • Zahlungsfrist: gilt erst für eine tatsächlich angelegte Buchung bzw. Bestellung. In meinem Aufbau: drei Tage.

Der Hold braucht deshalb einen eigenen Ablaufzeitpunkt, zum Beispiel hold_expires_at. Ist dieser Zeitpunkt überschritten und wurde keine verbindliche Bestellung erzeugt, darf die Ressource automatisch wieder freigegeben werden.

Bei einer Bestellung mit offener Zahlung ist die Situation anders: Hier bleibt der Platz reserviert, bis bezahlt oder die Zahlungsfrist überschritten ist. Am Fälligkeitstag kann eine Erinnerung versendet werden; danach wird die Buchung storniert und der Platz wieder freigegeben.

4. Status statt Boolean

Ein einzelnes Feld wie belegt=true reicht für reale Abläufe nicht. Sinnvoller ist ein Statusmodell, zum Beispiel:

  • frei
  • temporär gehalten
  • Zahlung offen
  • bezahlt / bestätigt
  • storniert
  • Anfrage offen
  • Anfrage genehmigt, Zahlung ausstehend
  • Anfrage abgelehnt

Die Verfügbarkeitsprüfung berücksichtigt nur die Status, die wirklich blockieren sollen. Eine abgelehnte oder abgelaufene Anfrage darf natürlich keinen Platz sperren.

5. Wenn die Hauptressource voll ist: Anfrage-Fallback statt Fehlermeldung

Besonders nützlich ist ein zweiter Weg, wenn die normale Ressource nicht verfügbar ist. Statt nur „kein Platz frei“ anzuzeigen, kann WordPress eine unverbindliche Anfrage anbieten.

In meinem Parkplatzsystem gibt es deshalb:

  • P1 als regulär online buchbaren Platz,
  • P2 nur auf Anfrage,
  • eine eigene Anfrage-Liste außerhalb der normalen WooCommerce-Bestellungen.

Das ist wichtig: Eine Anfrage ist noch keine Bestellung. Erst wenn sie geprüft und genehmigt wurde, entsteht daraus ein Angebot bzw. eine Zahlungsaufforderung. So bleibt der administrative Status eindeutig.

Warum eine eigene Anfrage-Liste sinnvoll ist

Wenn jede unverbindliche Anfrage sofort als WooCommerce-Bestellung angelegt wird, vermischt man Verkaufsdaten mit Interessenten. Besser ist eine eigene Datenstruktur mit mindestens:

  • Name und Kontakt,
  • gewünschtem Zeitraum,
  • angefragter Ressource,
  • Status,
  • Erstellungszeit,
  • Ablaufzeit eines späteren Angebots,
  • Verknüpfung zur Bestellung, sobald tatsächlich eine entsteht.

6. Genehmigte Anfrage nicht sofort als „gebucht“ behandeln

Auch eine genehmigte Anfrage ist noch nicht bezahlt. Deshalb braucht sie einen Zwischenstatus wie genehmigt – Zahlung offen. Erst nach Zahlungseingang wird die Buchung endgültig bestätigt.

Zusätzlich kann das Angebot selbst nur begrenzte Zeit gültig sein. Im Praxisfall war der Zahlungslink aus einer P2-Anfrage einen Tag gültig. Läuft diese Frist ab, sollte der Sonderplatz wieder für andere Anfragen freigegeben werden.

7. Verfügbarkeit und Preisberechnung müssen dieselben Daten verwenden

Ein gefährlicher Fehler entsteht, wenn Preisberechnung und Verfügbarkeitsprüfung unterschiedliche Zeiträume verwenden. Der Preis darf nicht auf dem reinen Reisezeitraum basieren, wenn die Ressource in Wahrheit schon früher und länger blockiert wird.

Der saubere Ablauf lautet:

  1. Eingaben validieren.
  2. Realen Blockzeitraum berechnen.
  3. Verfügbarkeit inklusive Puffer und Holds prüfen.
  4. Erst danach den Preis berechnen.
  5. Preis und geprüften Zeitraum gemeinsam in der Session speichern.
  6. Beim finalen Checkout die Verfügbarkeit noch einmal serverseitig prüfen.

Wichtig: Die erste Verfügbarkeitsanzeige ist keine Garantie. Unmittelbar vor dem Anlegen einer Buchung muss WordPress erneut prüfen, ob der Platz inzwischen von einem anderen Prozess blockiert wurde.

8. Eine Buchung braucht eine eindeutige Ressourcen-ID

Auch bei nur zwei Parkplätzen sollte man nicht mit Freitext wie „Parkplatz vorne“ oder „P2“ allein arbeiten. Jede Ressource bekommt eine stabile interne ID. Bezeichnung und Darstellung dürfen sich später ändern, ohne dass historische Buchungen kaputtgehen.

resource_id: 1
resource_code: P1
resource_name: Parkplatz 1

9. Kalenderanzeige ist nur die Oberfläche der Logik

Ein Monatskalender mit grün = frei und rot = belegt ist hilfreich, aber er darf nie die eigentliche Datenquelle sein. Die Kalenderzelle wird aus derselben zentralen Verfügbarkeitsfunktion erzeugt wie Checkout und Anfrageformular.

So verhindert man den typischen Fehler: Kalender sagt „frei“, Checkout sagt „belegt“ – oder umgekehrt.

10. Mein bevorzugtes Datenmodell

Für kleine Systeme reicht häufig schon eine eigene Tabelle oder ein Custom Post Type mit folgenden Kernfeldern:

id
resource_id
customer_id / contact
requested_start
requested_end
blocked_start
blocked_end
status
hold_expires_at
payment_due_at
offer_expires_at
order_id
created_at
updated_at

Die berechneten Blockzeiten speichere ich zusätzlich zu den Kundeneingaben. Dadurch kann man später nachvollziehen, warum ein Zeitraum als belegt galt.

Fazit

Bei knappen Ressourcen ist die eigentliche Herausforderung nicht der Kalender, sondern die Zustandsmaschine dahinter. Wer Nutzungszeit, Puffer, Hold, Zahlungsfrist und Anfrage sauber trennt, kann auch mit sehr wenigen Ressourcen zuverlässig verkaufen, ohne Überbuchungen und ohne unnötige WooCommerce-Bestellungen.

Das Muster funktioniert nicht nur für Parkplätze, sondern genauso für Besprechungsräume, Maschinen, Ferienobjekte, Mietgeräte oder andere Ressourcen, bei denen eine einzelne Überschneidung bereits ein echtes Problem wäre.