Application mobile Transporter
TST Terschelling
Kiewiet Rijwielverhuur BV
Sur l'île néerlandaise d'Ameland, où il n'y a pas de voiture, presque tous les touristes arrivent en ferry et ont immédiatement besoin d'un vélo. Kiewiet Cyclisme est la boutique de location la plus établie de l'île et derrière leur site Web se trouve une plateforme spécialement conçue qui gère chaque couche de l'entreprise: réservations en ligne, transport de bagages depuis le quai de ferry, ventes au détail, partenariats avec des agents, traitement des paiements, génération de factures et une flotte de terminaux de bureau sur le sol de l'atelier.
La plupart des plateformes de réservation résolvent un problème. Kiewiet a besoin de quatre résolus simultanément et liés ensemble en une seule expérience cohérente.
Les quatre flux s'enfoncent dans le même panier, la même passerelle de paiement, le même système de numérotation des commandes et le même moteur de facturation.
La plateforme est construite sur PHP 8.4 sans cadre externe sans Laravel, sans Symfony. Chaque couche de routage à ORM a été conçue et écrite spécifiquement pour ce domaine.
Chaque URL publique est saisie à travers un seul index.php point d'entrée. Apache .htaccess réécrit toutes les requêtes non fichières vers elle. À partir de là, un routeur personnalisé (NSSwitchBoard) analyse jusqu'à six niveaux de chemin URI, correspond au premier segment contre une table de routage et charge le contrôleur de page approprié. Routes non reconnues reçoivent une 400 mauvaise demande et redirigent vers la maison pas de pages d'erreur génériques fuyant des traces de pile.
Chaque contrôleur de page est une classe qui étend une base partagée (CyclismeWebPage) et est stockée en tant que singleton à l'intérieur de la session PHP. Cela signifie que l'état de l'ensemble de la page le panier d'achat, les dates sélectionnées, les filtres de produits actifs, l'état de connexion de l'agent persiste sur les demandes sans une seule base de données lue pour l'état de l'interface utilisateur. Un client peut naviguer pendant dix minutes, ajouter des vélos de plusieurs catégories, puis procéder au paiement, et le serveur reconstruit l'état exact de sa session à partir de la mémoire à chaque demande.
Les modèles de base de données suivent un schéma de deux fichiers. Un fichier _base.php est généré automatiquement à partir du schéma de base de données et ne contient que des propriétés typées et une plaque de chauffage getter/setter il n'est jamais édité manuellement. Un fichier complémentaire ajoute la logique commerciale en haut. Cette séparation signifie que les changements de schéma se régénèrent de manière propre sans toucher au code personnalisé.
La classe ORM de base (NSPersistentObject) implémente le suivi de l'état sale via un mécanisme dual: un drapeau sale explicite que le code définit manuellement, et une somme de contrôle MD5 d'arrière-plan de toutes les propriétés sérialisées prises au moment du chargement. Si le drapeau est réglé ou si la somme de contrôle a changé, un appel de stockage) persistera. Cela empêche une double écriture accidentelle et rend les mises à jour partielles sûres.
La logique de stockage détermine INSERT versus UPDATE au moment de l'exécution en vérifiant si l' UUID de l'enregistrement existe déjà dans la base de données. Il n'y a pas de méthodes de création et de mise à jour séparées décide l'ORM. Les collections de modèles liées (par exemple, les éléments de ligne à l'intérieur d'une réservation) sont attachées aux objets parents et enregistrées dans un seul appel coordonné.
Un constructeur de requêtes léger (DataStore) enveloppe l'ORM avec une paramétrisation positionnelle. Les requêtes ressemblent à la requête (("Groep_uuid=:1 AND Verhuur>0", $groupUUID). Le constructeur résout automatiquement les types Intégres passent à travers les unscaped, les chaînes passent par la fonction d'échappement de la base de données, les matrices s'étendent dans les listes SQL IN (...) et les types personnalisés de date/heure se sérialisent au format MySQL. Le résultat est une sélection entité un itérateur paresseux ou désireux sur des objets modèles entièrement chargés.
EntitySelection prend également en charge les opérations des ensembles de mémoire: intersection, union et différence des ensembles de résultats à l'aide de listes d'identifiants. Cela permet de combiner plusieurs requêtes sans aller-retour supplémentaires.
Une bicyclette réservée du mardi au vendredi n'est pas disponible pour une période de chevauchement mais une bicyclette retournée lundi matin peut être disponible lundi après-midi si le temps de retour le permet. Cette matrice alimente l'interface utilisateur du calendrier en temps réel sur la page de réservation.
Les prix dépendent du groupe de produits, du nombre de jours de location et de la période de l'année. Un modèle de prix séparé (Prijzen) stocke des règles en fonction du groupe, de la durée et de la date. Le moteur résout le prix applicable pour chaque article de panier à la caisse. Les comptes d'agents reçoivent un groupe de prix séparé, de sorte que la même moto peut avoir un tarif différent selon qu'elle soit réservée directement ou par l'intermédiaire d'une agence partenaire.
Les codes de réduction promotionnels sont validés en temps réel pendant le flux de réservation, avec des règles d'expiration, la valeur minimale de commande et les catégories de produits applicables.
Le flux de transport de bagages est restreint par le calendrier de ferry Wagenborg. Les clients sélectionnent un passage de ferry et le système valide que le temps de transport demandé est compatible avec cette navigation. Le calendrier de ferry est récupéré et répertorié à partir d'une API externe par une classe d'usine dédiée (FerryScheduleFactory) et stocké localement pour des recherches rapides.
La plateforme intègre deux passerelles de paiement: OGone (Ingenico) et EMS. Les clients choisissent leur méthode préférée à la caisse.
Les numéros de commande sont générés par une usine qui maintient des comptoirs par type de commande (réservations, ventes en magasin, commandes de bagages, factures) avec des réinitialisations automatiques sur les limites de l'année et du mois.
La plateforme génère des documents PDF de manière native pour trois types de documents:
Une classe PostOffice enveloppe PHPMailer pour les envoyer en tant que pièces jointes de courrier électronique aux côtés de messages de confirmation HTML modélisés.
Un terminal séparé REST API (ApiEntrypoint.php) sert une application mobile de numérisation utilisée par le personnel de transport sur l'île. Authentifiée via une en-tête X-Auth-Token, elle expose les terminaux pour le chargement des données du colis, la soumission de numérisations de livraison et la mise à jour de l'emplacement du colis. Chaque événement de numérisation met à jour l'état de l'enregistrement des bagages en temps réel et est visible aux clients qui vérifient leur commande.
La plateforme Web ne fonctionne pas en isolement. Elle s'exécute sur environ vingt postes de travail dans les magasins de Kiewiet sur Ameland est une application de bureau parallèle construite dans 4D v20R8 une plateforme d'application relationnelle complète avec sa propre interface utilisateur, sa couche de données et sa logique commerciale.
L'application de bureau gère l'ensemble des aspects opérationnels de l'entreprise: le personnel vérifie les clients et remet des vélos à l'aide de bons de location (VerhuurBonnen), procéder à des réparations à pied, gérer l'inventaire des pièces d'atelier, gérer la caisse au point de vente et planifier des itinéraires de transport de bagages.
Les deux systèmes partagent une base de données MySQL, mais ils ne partagent pas simplement une connexion. Les changements circulent à travers une couche de synchronisation dédiée construite sur une table RecordSync qui agit comme un journal de changement.
Lorsque la boutique en ligne PHP crée ou modifie un enregistrement une nouvelle réservation, une confirmation de paiement, un nouveau client il écrit une entrée à RecordSync avec un numéro de séquence et l'identificateur de l'application "Online". Un processus d'arrière-plan dans les sondages d'application 4D pour ces changements via bridge.PHP, récupère les enregistrements modifiés et les insère dans le 4D stock de données en utilisant ORDA (Accès aux données relationnelles objet). Le nombre de séquences ne progresse qu'après un traitement réussi, ce qui garantit qu'aucun changement n'est manqué.
Le chemin inverse fonctionne à travers le système de déclenchement de 4D. Toutes les tables de base de données 62 ont des déclencheurs qui déclenchent chaque sauvegarde et déléguent à un orchestrateur central DataBridge. Cela fait la queue du changement en tant qu'entrée RecordSync, et le processus d'arrière-plan le pousse à passer.php, ce qui le renvoie en MySQL. Sur HTTP 200, les entrées de synchronisation sont abandonnées.
L'architecture de déclenchement utilise SharedStorage pour la communication interprocessus à l'intérieur du temps d'exécution 4D, et un mécanisme de déduplication empêche le même changement d'enregistrement d'être mis en file plusieurs fois lors de sauvegardes successives rapides.
La planification du transport de bagages dans l'application de bureau comprend un moteur d'optimisation des itinéraires. Une classe BaggageRouting attribue des colis aux circuits de transport, et une intégration OpenRouteService demande à l'API ORS de calculer les distances routières.interstops).
Le système de conception est construit sur 32 partiels SCSS organisés à partir de jetons de conception à travers les couches de composants à la mise en page spécifique. Les propriétés personnalisées portent l'ensemble complet de jetons couleurs, espacement, typographie de sorte que les changements de conception se propagent de manière cohérente. La feuille de style compilée fonctionne à environ 49 KB.
Le comportement interactif est géré par 26 modules JavaScript couvrant le calendrier de réservation, le tiroir du panier, le filtrage des produits, la sélection du temps de bagages, les flux de travail des agents et les flux de paiement. Le calendrier intègre FullCalendar.js avec un rendu de disponibilité personnalisé basé sur les données de la matrice de disponibilité PHP. Moment.js gère toute l'arithmétique de la date du côté client, en synchronisation avec la validation de la date du côté serveur.
La communication AJAX entre le frontend et le backend utilise un format de réponse structuré: chaque réponse comporte un statut, une URL redirigée optionnelle, un ensemble de fragments HTML avec des cibles de sélection CSS pour l'injection DOM basée sur jQuery et un ensemble de données de champ pour la population de formulaires. Cela rend le code client uniforme.
Un backend administrateur séparé (office.php) fournit une interface de gestion complète pour l'entreprise: la navigation et l'édition de tous les modèles de données, la gestion des prix et de l'inventaire, la visualisation des réservations et des paiements, et la génération de rapports commerciaux. Ceux-ci alimentent un moteur de rendu dynamique de listes et de formulaires, de sorte que l'ajout d'un nouveau champ à la base de données se propage automatiquement à l'interface d'administrateur sans changement de modèle.
Kiewiet n'est pas un site Web avec un widget de réservation. C'est une plate-forme opérationnelle complète conçue pour spécifier une entreprise réelle et saisonnière où la marge d'erreur est faible un vélo double réservé ou une livraison de bagages manquée affecte les vacances d'une famille. Chaque couche, de la table de routage au moteur de synchronisation à l'optimisateur de route, a été conçue et construite pour servir cette réalité.
Les décisions techniques un ORM personnalisé avec des checksums d'état sale, un graphique d'objet de page persistant de session, un journal de changement bidirectionnel entre deux temps d'exécution hétérogènes n'ont pas été prises pour l'élégance. Elles ont été prises parce que l'entreprise exigeait exactement ce comportement et qu'aucune solution off-the-shelf ne l'offrait.
Autres réalisations
TST Terschelling
TransportMaster
TransportMaster