Softwareentwicklung

Kiewiet-Buchungssystem, Onlineshop und Gepäcktransport

Kiewiet Rijwielverhuur BV

Auftraggeber
Kiewiet Rijwielverhuur BV
Kategorie
Softwareentwicklung
Jahr
2025
Technologien
4DPHPJavascriptHTMLCSS
Kiewiet Fahrradsport Full-Stack-Buchungsplattform für Amelands größte Fahrradvermietung

Auf der niederländischen Autofreien Insel Ameland kommt fast jeder Tourist mit der Fähre an und braucht sofort ein Fahrrad. Kiewiet Cyclisme ist der etablierteste Mietgeschäft der Insel und hinter ihrer Website befindet sich eine speziell gebaute Plattform, die jede Ebene des Geschäfts verwaltet: Online-Reservierungen, Gepäcktransport vom Fährdock, Einzelhandel, Agentenpartnerschaften, Zahlungsabwicklung, Rechnungsgenerierung und eine Flotte von Desktop-Terminalen auf der Werkstatt. Dies ist die Geschichte dieses Systems.


Die Herausforderung: Eine Plattform, vier Geschäftslinien

Die meisten Buchungsplattformen lösen ein Problem. Kiewiet benötigte vier gleichzeitig gelöst und in einer einzigen kohärenten Erfahrung zusammengebunden.

  1. Fahrradvermietung Kunden wählen Daten aus, durchsuchen Fahrradkategorien (Stadt, Berg, Elektro, Tandem, Kinder) und reservieren spezifische Einheiten aus dem Live-Inventar. Die Preise variieren je nach Gruppenart, Mietdauer und Datumsbereich. Die Verfügbarkeit wird in Echtzeit mit einer Matrix aller aktuellen Reservierungen berechnet.
  2. Gepäcktransport Ankommende Touristen können die Gepäcklieferung von Haustür zu Haustür vom Fährhafen zu ihrer Urlaubsadresse buchen.
  3. Einzelhandelsgeschäft Helme, Schlösser, Lichter, Kindersitze und Zubehör werden über einen separaten Shopflow verkauft, der den gleichen Warenkorb wie die Mietbuchung teilt und eine einzige kombinierte Bestellung und Rechnung erzeugt.
  4. Agent- und Partnernetzwerk Registrierte Reisebüros und Unterkunftspartner melden sich an einem speziellen Portal an, buchen im Namen ihrer Gäste ein und erhalten agentenspezifische Preis- und Provisionsstrukturen.

Alle vier Ströme fließen in den gleichen Warenkorb, das gleiche Zahlungsgateway, das gleiche Bestellnummernsystem und die gleiche Rechnungsmaschine.


Die Architektur: Ein benutzerdefinierter Rahmen aus ersten Grundsätzen

Die Plattform basiert auf PHP 8.4 ohne externes Framework ohne Laravel, ohne Symfony. Jede Schicht vom Routing bis zu ORM wurde speziell für diese Domain entwickelt und geschrieben.

Anforderung Lebenszyklus

Jeder öffentliche URL wird über einen einzigen Index eingegeben.php Entrypoint. Apache's .htaccess schreibt alle nicht-Dateianfragen darauf um. Von dort aus analysiert ein benutzerdefinierter Router (NSSwitchBoard) bis zu sechs URI-Pfadniveaus, passt das erste Segment mit einer Routing-Tabelle zusammen und lädt den entsprechenden Seitencontroller. Unerkannte Routen erhalten eine 400-Bad Request und leiten sie nach Hause weiter keine generischen Fehlerseiten, die Stapelspuren durchlecken.

Jeder Seitencontroller ist eine Klasse, die eine geteilte Basis (CyclismeWebPage) erweitert und als Singleton innerhalb der PHP-Sitzung gespeichert wird. Dies bedeutet, dass der gesamte Seitenzustand den Einkaufswagen, ausgewählte Daten, aktive Produktfilter, Agent-Login-Status über Anfragen ohne eine einzige Datenbank für den Benutzeroberflächenszustand gelesen bleibt. Ein Kunde kann zehn Minuten lang durchsuchen, Fahrräder aus mehreren Kategorien hinzufügen, dann zur Zahlung gehen, und der Server rekonstruiert den genauen Zustand seiner Sitzung aus dem Gedächtnis bei jeder Anfrage.

Die ORM-Schicht

Datenbankmodelle folgen einem Doppel-Datei-Muster. Eine _base.php-Datei wird automatisch aus dem Datenbank-Schema generiert und enthält nichts als getippte Eigenschaften und getter/setter-Boilerplate es wird nie manuell bearbeitet. Eine Begleitdatei fügt Business-Logik oben hinzu. Diese Trennung bedeutet, dass Schemaänderungen sauber regenerieren, ohne benutzerdefinierten Code zu berühren.

Die Basis-ORM-Klasse (NSPersistentObject) implementiert die Verfolgung des schmutzigen Zustands durch einen doppelten Mechanismus: eine explizite schmutzige Flagge, die der Code manuell setzt, und eine Hintergrund-MD5-Checksum aller serialisierten Eigenschaften, die zur Ladezeit aufgenommen wurden.

Die Speicherlogik bestimmt INSERT gegen UPDATE in der Laufzeit, indem sie überprüft, ob die UUID der Aufzeichnung bereits in der Datenbank vorhanden ist. Es gibt keine getrennten Erstellungs- und Aktualisierungsmethoden die ORM entscheidet. Verknüpfte Modellkollektionen (z.B. die Zeilen innerhalb einer Reservierung) werden zu den Hauptobjekten angeschlossen und in einem einzigen koordinierten Aufruf gespeichert.

Der Fragesteller

Ein leichtgewichtiger Abfragenbauer (DataStore) wendet den ORM mit positionale Parameterisierung zusammen. Abfragen sehen aus wie Abfragen("Groep_uuid=:1 AND Verhuur>0", $groupUUID). Der Builder löst Typen automatisch Vollzählungen passieren durch das Datenbank-Escape-Funktion, Arrays erweitern sich in SQL IN (...)-Listen und benutzerdefinierte Datum/Zeit-Typen serialisieren sich in MySQL-Format. Das Ergebnis ist ein EntitySelection ein fauler oder eifriger Iterator über voll geladene Modellobjekte.

EntitySelection unterstützt auch die Operationen der In-Memory-Sets: Kreuzung, Vereinigung und Differenz der Ergebnissets mit ID-Listen. Dies ermöglicht die Kombination mehrerer Abfragen ohne zusätzliche Rundfahrten.


Die Buchungsmaschine

Verfügbarkeitsmatrix

Eine Fahrrad, die von Dienstag bis Freitag reserviert ist, ist für eine überlappende Periode nicht verfügbar aber eine am Montagmorgen zurückgegebene Fahrrad kann am Montagnachmittag verfügbar sein, wenn die Umlaufzeit es zulässt. Diese Matrix unterstützt die Echtzeitkalenderinterface der Buchungsseite.

Preismatrix

Die Preise hängen von der Produktgruppe, der Anzahl der Miettage und der Jahreszeit ab. Ein separates Preismodell (Prijzen) speichert Regeln nach Gruppe, Dauerbereich und Datumsbereich. Der Motor löst den anwendbaren Preis für jeden Warenkorb bei der Kasse. Agentenkonten erhalten eine separate Preisgruppe, so dass das gleiche Fahrrad einen anderen Preis haben kann, je nachdem, ob es direkt oder über eine Partneragentur gebucht wird.

Aktionscodes

Werbe- und Rabattcodes werden während des Buchungsflusses in Echtzeit mit Regelungen für das Ablaufzeitraum, dem Mindestbestellwert und den anwendbaren Produktkategorien validiert.

Integration des Fährplans

Der Gepäcktransportfluss wird durch den Wagenborg-Fähre-Zeitplan eingeschränkt. Die Kunden wählen eine Fähreüberfahrt aus und das System bestätigt, dass die angeforderte Transportzeit mit dieser Segelung kompatibel ist. Der Fähre-Zeitplan wird von einer externen API von einer speziellen Fabrikklasse (FerryScheduleFactory) abgeholt und für schnelle Suchen lokal gespeichert.


Zahlungsabwicklung

Die Plattform integriert zwei Zahlungs-Gateways: OGone (Ingenico) und EMS. Kunden wählen ihre bevorzugte Methode bei der Kasse. Nach dem Gateway-Callback bestätigt Reservering::ValidateReservation() den Zahlungsstatus, generiert die Bestellnummer, schreibt die Reservierung in die Datenbank, generiert eine PDF-Bestätigung und versendet eine Bestätigungs-E-Mail alles in einer atomaren Nachzahlungssekvenz.

Bestellnummern werden von einer Fabrik generiert, die Zähler pro Bestelltyp (Reservierungen, Shopverkäufe, Gepäckbestellungen, Rechnungen) mit automatischen Resets an den Jahres- und Monatsgrenzen aufhält.


Dokumentengenerierung

Die Plattform generiert PDF-Dokumenten nativ für drei Dokumentartypen:

  • Reservierungsbestätigungen Reservierungsaufschluss mit Daten, Fahrrädern, Preisverteilung und Bestellnummer.
  • Rechnungen Mehrwertsteuerrechnungen für alle Bestelltypen.
  • Gepäcketiketten Auf den Paketen für das Scannen an jedem Transportkontrollpunkt gedruckte QR-Code-Labels befestigt.

Eine PostOffice-Klasse wickelt PHPMailer ein, um diese als E-Mail-Anhänge zusammen mit vorgestellten HTML-Bestätigungsnachrichten zu versenden.


Die Gepäckverfolgungs-API

Ein separater REST API Endpunkt (ApiEntrypoint.php) dient einer mobilen Scanning-Anwendung, die von Transportpersonal auf der Insel verwendet wird. Über einen X-Auth-Token-Header authentifiziert, stellt er Endpunkte für das Laden von Paketdaten, die Übermittlung von Lieferscans und die Aktualisierung von Paketstandorten dar. Jedes Scannenverhalten aktualisiert den Status der Gepäckunterlagen in Echtzeit und ist für Kunden sichtbar, die ihre Bestellung überprüfen.


Die Desktop-Anwendung 4D auf zwanzig Maschinen

Die Webplattform funktioniert nicht isoliert. Die App läuft über etwa zwanzig macOS-Arbeitsstationen in Kiewiet-Geschäftsstandorten auf Ameland und ist eine parallele Desktop-Anwendung, die in 4D v20R8 eine vollständige relationale Anwendungsplattform mit eigener Benutzeroberfläche, Datenschicht und Geschäftslogik aufgebaut ist.

Die Desktop-App verwaltet die gesamte operative Seite des Unternehmens: Mitarbeiter checken die Kunden ein und übergeben Fahrräder mit Mietgutscheinen (VerhuurBonnen), die Reparaturen durchführen, die Inventar von Werkstattteilen verwalten, die Kasse des Verkaufsstationsbetriebs betreiben und Gepäcktransportruten planen.

Die zweiseitige Synchronisierungsmaschine

Beide Systeme teilen eine MySQL Datenbank, aber sie teilen nicht einfach eine Verbindung. Änderungen fließen durch eine dedizierte Synchronisierungs-Schicht, die auf einer RecordSync-Tabelle aufgebaut ist und als Change Journal fungiert.

Wenn der PHP-Webshop eine Aufzeichnung erstellt oder ändert eine neue Reservierung, eine Zahlungsbestätigung, einen neuen Kunden schreibt er einen Eintrag in RecordSync mit einer Sequenznummer und dem Anwendungsidentifikator "Online". Ein Hintergrundprozess in den 4D-Anwendungsumfragen für diese Änderungen über die Bridge.PHP, holt die modifizierten Aufzeichnungen und stellt sie mit ORDA (Object Relational Data Access) in den 4D-Datenlager. Die Sequenzzahl geht erst nach erfolgreicher Verarbeitung voran und sorgt dafür, dass keine Veränderung verpasst wird.

Der umgekehrte Pfad funktioniert durch das Trigger-System von 4D. Alle 62 Datenbanktabellen haben Trigger, die bei jedem Speichern und Delegieren an einen zentralen DataBridge-Orchestrator abfeuern. Dies stellt die Änderung als RecordSync-Eintrag in Schlange und der Hintergrundprozess drängt sie zur Brücke.php, die sie in MySQL umschaltet. Auf HTTP 200 werden die Synchronisierungseinträge fallen gelassen.

Die Trigger-Architektur verwendet SharedStorage für die Kommunikation zwischen Prozessen innerhalb der 4D Laufzeit, und ein Deduplikationsmechanismus verhindert, dass die gleiche Rekordänderung mehrmals während schnellen aufeinanderfolgenden Speichern in der Warteschlange gestellt wird.

Optimierung der Strecke

Die Gepäcktransportplanung in der Desktop-App beinhaltet eine Routenoptimierungs-Engine. Eine Gepäck-Routing-Klasse zugeordnet Pakete für Transportläufe, und eine OpenRouteService-Integration befragt die ORS-API, um Straßenabstände zu berechnen. Eine Routing-Klasse implementiert einen 2-Opt-Verbesserungs-Algorithmus, um die Gesamtlänge der Route über alle Zwischenstationen zu minimieren (Zwischenstoppungen).


Das Frontend

Das Design-System basiert auf 32 SCSS-Teilen, die von Design-Token über Komponentenschichten bis hin zu seitenspezifischen Layouts organisiert sind.

Interaktives Verhalten wird durch 26 JavaScript-Module verwaltet, die den Buchungskalender, den Warenkorb, die Produktfilterung, die Gepäckzeitwahl, die Agent-Workflows und die Zahlungsströme abdecken. Der Kalender integriert FullCalendar.js mit benutzerdefiniertem Verfügbarkeitsrenderning, das von Daten aus der PHP-Verfügbarkeitsmatrix angetrieben wird. Moment.js verwaltet alle Datenarithmetik auf der Clientseite, die in Synchronisierung mit der Serverseiten-Datumvalidierung gehalten wird.

Die AJAX-Kommunikation zwischen Frontend und Backend verwendet ein strukturiertes Reaktionsformat: jede Reaktion trägt einen Status, eine optionale Umleitung URL, eine Reihe von HTML-Fragmenten mit CSS-Selektorzielen für jQuery-getriebene DOM-Injektion und ein Feld-Daten-Array für die Formularpopulation. Dies macht den Client-Code einheitlich.


Das Verwaltungsbüro

Ein separates Admin-Backend (office.php) bietet eine vollständige Management-Schnittstelle für das Unternehmen: Browsing und Bearbeitung aller Datenmodelle, Verwaltung von Preisen und Inventar, Anzeige von Reservierungen und Zahlungen und Erstellung von Geschäftsberichten. Das Büro verwendet 65+ JSON Schema-Dateien eine pro Tabelle , die Feldanzeigennamen, Datentypen, Sortierenverhalten und Listensichtlichkeit definieren. Diese betreiben eine dynamische Liste und Form-Rendering-Engine, so dass das Hinzufügen eines neuen Feldes zur Datenbank sich automatisch ohne Vorlageänderungen in die Admin-Schnittstelle ausbreitet.


Durch die Zahlen

  • 233 PHP Quelldateien
  • 70+ Datenmodelle für alle Geschäftsbereiche
  • 65+ Datenbanktabellen
  • 50 Kernrahmenbasisklassen
  • 11 öffentlich zugeschnittene Seitencontroller
  • 32 SCSS-Teile in ein kohärentes Entwurfssystem zusammengestellt
  • 26 JavaScript-Funktionsmodule
  • 96 4D Klassendateien und 1.099 vergangene Methoden in der Desktop-Anwendung
  • 62 Datenbank-Trigger, die das zweiseitige Synchronisierungssystem versorgen
  • ~ 20 Desktop-Arbeitsstationen, die die 4D-Anwendung an mehreren Standorten auf Ameland ausführen
  • Zwei Zahlungsgateways, drei PDF-Dokumenttypen, eine REST-Tracking-API

Was dieses Projekt darstellt

Kiewiet ist keine Website mit einem Buchungs-Widget. Es ist eine vollständige Betriebsplattform, die für ein echtes, saisonal intensives Geschäft gebaut wurde, in dem der Fehlermargin gering ist ein doppelt gebuchtes Fahrrad oder eine verpasste Gepäcklieferung beeinflusst den Urlaub einer Familie. Jede Schicht, vom Routing-Tisch über die Synchronisationsmaschine bis zum Routenoptimierer, wurde entworfen und gebaut, um dieser Realität zu dienen.

Die technischen Entscheidungen ein benutzerdefinierter ORM mit schmutzigen Zustand-Checksums, eine Sitzung-persistent Seite-Objekt-Grafik, ein zweiseitiger Wechsel-Journal zwischen zwei heterogenen Laufzeiten wurden nicht für Eleganz getroffen. Sie wurden getroffen, weil das Unternehmen genau dieses Verhalten verlangte und keine off-the-shelf-Lösung bot es.

Projektbilder

Weitere Arbeiten

Ähnliche Projekte

Starten Sie ein Projekt mit uns

Sie haben ein Projekt im Sinn? Sprechen wir darüber, wie wir Sie unterstützen können.