Développement logiciel

Kiewiet : système de réservation, boutique et transport de bagages

Kiewiet Rijwielverhuur BV

Client
Kiewiet Rijwielverhuur BV
Catégorie
Développement logiciel
Année
2025
Technologies
4DPHPJavascriptHTMLCSS
Kiewiet Cyclisme — Une plateforme de réservation complète pour le plus grand loueur de vélos d’Ameland

Sur l’île néerlandaise d’Ameland, presque tous les touristes arrivent en ferry et ont immédiatement besoin d’un vélo. Kiewiet Cyclisme est le loueur le mieux établi de l’île. Derrière son site se trouve une plateforme conçue sur mesure pour gérer toutes les dimensions de l’activité : réservations en ligne, transport des bagages depuis le débarcadère, ventes en boutique, partenariats avec les agences, paiements, facturation et parc de postes de bureau dans les magasins. Voici l’histoire de ce système.


Le défi : une plateforme pour quatre activités

La plupart des plateformes de réservation répondent à un seul besoin. Kiewiet devait en couvrir quatre simultanément et les réunir dans une expérience cohérente.

  1. Location de vélos — Les clients choisissent leurs dates, parcourent les catégories de vélos — ville, VTT, électriques, tandems et enfants — puis réservent des unités précises dans le parc disponible. Les tarifs varient selon la catégorie, la durée de location et la période. La disponibilité est calculée en temps réel à partir d’une matrice de toutes les réservations en cours.
  2. Transport de bagages — Les touristes peuvent réserver la livraison de leurs bagages du port de ferry jusqu’à leur hébergement. Les commandes tiennent compte des horaires des ferries, des tarifs selon le poids, des étiquettes de suivi à code QR et d’une logique d’optimisation qui affecte chaque colis à une tournée.
  3. Boutique — Casques, antivols, éclairages, sièges pour enfants et accessoires sont vendus dans un parcours distinct, mais partagent le même panier que les locations pour produire une commande et une facture communes.
  4. Réseau d’agences et de partenaires — Les agences de voyages et hébergeurs inscrits se connectent à un portail dédié, réservent pour leurs clients et bénéficient de tarifs et de commissions propres à leur compte.

Ces quatre parcours utilisent 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.


L’architecture : un framework entièrement conçu sur mesure

La plateforme repose sur PHP 8.4, sans framework externe : ni Laravel ni Symfony. Chaque couche, du routage à l’ORM, a été conçue et écrite pour ce domaine métier.

Cycle de traitement des requêtes

Toutes les URL publiques passent par un point d’entrée index.php unique. Le fichier .htaccess d’Apache y redirige les requêtes qui ne désignent pas un fichier. Un routeur sur mesure, NSSwitchBoard, analyse ensuite jusqu’à six niveaux du chemin URI, cherche le premier segment dans une table de routage et charge le contrôleur de page approprié. Les routes inconnues reçoivent une réponse 400 Bad Request et sont redirigées vers l’accueil, sans page d’erreur générique exposant une trace d’exécution.

Chaque contrôleur de page est une classe dérivée d’une base partagée, CyclismeWebPage, et conservée comme instance unique dans la session PHP. L’ensemble de l’état de la page — panier, dates choisies, filtres produits actifs et connexion de l’agence — persiste ainsi entre les requêtes, sans lecture de base de données pour l’état de l’interface. Un client peut parcourir le site pendant dix minutes, ajouter des vélos de plusieurs catégories puis passer au paiement : le serveur reconstitue précisément l’état de sa session depuis la mémoire à chaque requête.

La couche ORM

Les modèles de données suivent une organisation en deux fichiers. Un fichier _base.php est généré automatiquement depuis le schéma de la base ; il ne contient que les propriétés typées et les accesseurs, et n’est jamais modifié manuellement. Un fichier complémentaire ajoute la logique métier. Cette séparation permet de régénérer les modèles après un changement de schéma sans toucher au code spécifique.

La classe ORM de base, NSPersistentObject, suit les modifications par deux mécanismes : un indicateur explicite positionné manuellement par le code et une somme de contrôle MD5 de toutes les propriétés sérialisées, calculée au chargement. Si l’indicateur est actif ou si la somme a changé, un appel à store() enregistre les données. Cela évite les doubles écritures accidentelles et sécurise les mises à jour partielles.

La logique d’enregistrement choisit entre INSERT et UPDATE à l’exécution en vérifiant si l’UUID de l’enregistrement existe déjà dans la base. Il n’y a pas de méthodes de création et de mise à jour distinctes : l’ORM décide. Les collections de modèles liés, par exemple les lignes d’une réservation, sont rattachées aux objets parents et enregistrées par un seul appel coordonné.

Le constructeur de requêtes

Un constructeur léger, DataStore, complète l’ORM avec un paramétrage positionnel. Les requêtes prennent la forme query("Groep_uuid=:1 AND Verhuur>0", $groupUUID). Les types sont déterminés automatiquement : les entiers sont transmis tels quels, les chaînes passent par la fonction d’échappement de la base, les tableaux sont développés en listes SQL IN (...) et les types de date et d’heure spécifiques sont sérialisés au format MySQL. Le résultat est une EntitySelection : un itérateur à chargement différé ou immédiat sur des objets de modèle entièrement chargés.

EntitySelection prend aussi en charge les opérations ensemblistes en mémoire : intersection, union et différence des résultats à partir de listes d’identifiants. Plusieurs requêtes peuvent ainsi être combinées sans échanges supplémentaires avec la base.


Le moteur de réservation

Matrice de disponibilité

La disponibilité à la location n’est pas un simple compteur de stock. Un vélo réservé du mardi au vendredi est indisponible pour toute période qui chevauche ces dates. En revanche, un vélo rendu le lundi matin peut être disponible le lundi après-midi si le délai de remise à disposition le permet. Producten::buildAvailabilityMatrix() calcule une grille de disponibilité par produit et par jour sur toute la période demandée, en tenant compte des réservations existantes. Cette matrice alimente le calendrier en temps réel de la page de réservation.

Matrice tarifaire

Les prix dépendent de la catégorie du produit, du nombre de jours de location et de la période de l’année. Un modèle tarifaire distinct, Prijzen, conserve les règles par catégorie, plage de durée et plage de dates. Le moteur détermine le prix applicable à chaque article du panier lors de la commande. Les comptes d’agences disposent d’un groupe tarifaire spécifique : un même vélo peut donc avoir un tarif différent selon qu’il est réservé directement ou par une agence partenaire.

Codes promotionnels

Les codes de réduction sont validés en temps réel pendant la réservation selon leur date d’expiration, le montant minimal de commande et les catégories de produits admissibles. La réduction déclenche le recalcul du total du panier et figure dans l’enregistrement de réservation ainsi que sur la facture.

Intégration des horaires de ferry

Le transport de bagages dépend des horaires de la compagnie Wagenborg. Le client choisit une traversée, puis le système vérifie que l’horaire de transport demandé est compatible. Une classe de fabrique dédiée, FerryScheduleFactory, récupère les horaires par une API externe, les insère ou les actualise et les conserve localement pour une consultation rapide.


Traitement des paiements

La plateforme intègre deux passerelles de paiement : OGone (Ingenico) et EMS. Les clients choisissent leur méthode à la commande. Après la notification de la passerelle, Reservering::ValidateReservation() confirme l’état du paiement, génère le numéro de commande, enregistre la réservation en base, produit une confirmation PDF et envoie l’e-mail de confirmation, au sein d’une séquence atomique unique après paiement.

Une fabrique génère les numéros de commande à l’aide de compteurs propres à chaque type — réservations, ventes en boutique, commandes de bagages et factures — avec remise à zéro automatique au changement d’année et de mois. Des zéros initiaux donnent aux numéros une longueur fixe et un format cohérent sur tous les documents.


Génération des documents

La plateforme génère directement trois types de documents PDF :

  • Confirmations de réservation — Récapitulatif avec les dates, les vélos, le détail des prix et le numéro de commande.
  • Factures — Factures avec détail des lignes et de la TVA pour tous les types de commandes.
  • Étiquettes de bagages — Étiquettes imprimées à code QR, fixées aux colis et scannées à chaque point de contrôle du transport.

Une classe PostOffice s’appuie sur PHPMailer pour les envoyer en pièces jointes avec des messages de confirmation HTML construits à partir de modèles.


L’API de suivi des bagages

Un point d’entrée REST distinct, ApiEntrypoint.php, dessert l’application mobile de lecture utilisée par les équipes de transport sur l’île. Authentifiée par l’en-tête X-Auth-Token, l’API propose des points de terminaison pour charger les données des colis, transmettre les lectures de livraison et actualiser leur emplacement. Chaque lecture met à jour l’état du bagage en temps réel et devient visible pour le client qui consulte sa commande.


L’application de bureau : 4D sur vingt postes

La plateforme web ne fonctionne pas seule. Une application de bureau développée en 4D v20R8 est utilisée sur environ vingt postes macOS dans les magasins Kiewiet d’Ameland. 4D est une plateforme applicative relationnelle complète, avec sa propre interface, sa couche de données et sa logique métier.

L’application de bureau gère toutes les opérations de l’entreprise : accueil des clients et remise des vélos à l’aide de bons de location (VerhuurBonnen), prise en charge des réparations sans rendez-vous, gestion des pièces de l’atelier, caisse et planification des tournées de bagages.

Le moteur de synchronisation bidirectionnelle

Les deux systèmes partagent une base MySQL, mais leur relation va au-delà d’une connexion commune. Les modifications passent par une couche de synchronisation dédiée reposant sur une table RecordSync qui sert de journal des changements.

Lorsque la boutique PHP crée ou modifie un enregistrement — réservation, confirmation de paiement ou nouveau client — elle ajoute une entrée dans RecordSync avec un numéro de séquence et l’identifiant d’application "Online". Un processus en arrière-plan dans 4D consulte ces changements via bridge.php, récupère les enregistrements modifiés et les insère ou les actualise dans les données 4D avec ORDA (Object Relational Data Access). Le numéro de séquence n’avance qu’après un traitement réussi, pour qu’aucune modification ne soit oubliée.

Le chemin inverse utilise les déclencheurs de 4D. Chacune des 62 tables possède des déclencheurs exécutés à chaque enregistrement, qui délèguent leur traitement à un orchestrateur central DataBridge. Celui-ci met le changement en file d’attente sous forme d’entrée RecordSync ; le processus en arrière-plan l’envoie ensuite à bridge.php, qui l’insère ou l’actualise dans MySQL. Après une réponse HTTP 200, les entrées de synchronisation sont supprimées.

Cette architecture utilise SharedStorage pour la communication entre processus dans l’environnement 4D. Un mécanisme de déduplication empêche qu’une même modification soit mise en attente plusieurs fois lors d’enregistrements successifs rapprochés.

Optimisation des tournées

La planification du transport des bagages dans l’application de bureau intègre un moteur d’optimisation. La classe LuggageRouting affecte les colis aux tournées et une intégration OpenRouteService interroge l’API ORS pour calculer les distances routières. Une classe Routing applique un algorithme d’amélioration 2-opt afin de réduire la longueur totale du parcours entre les arrêts intermédiaires (tussenstops).


L’interface web

Le système de design est réparti en 32 fichiers partiels SCSS, des variables de design aux composants et aux mises en page propres à chaque page. Les propriétés personnalisées regroupent toutes les valeurs — couleurs, espacements et typographie — pour répercuter les changements de façon cohérente. La feuille de style compilée représente environ 49 Ko.

Les interactions sont gérées par 26 modules JavaScript : calendrier de réservation, panneau du panier, filtres produits, choix des horaires de bagages, parcours des agences et paiements. Le calendrier associe FullCalendar.js à un affichage personnalisé des disponibilités alimenté par la matrice PHP. Moment.js réalise les calculs de dates côté client en cohérence avec les validations côté serveur.

Les échanges AJAX utilisent un format de réponse structuré : un statut, une URL de redirection facultative, un tableau de fragments HTML associés à des sélecteurs CSS pour leur insertion dans le DOM par jQuery et un tableau de valeurs pour renseigner les formulaires. Le code client traite ainsi une seule structure de réponse, quelle que soit l’action appelée sur le serveur.


L’interface d’administration

Un backend d’administration distinct, office.php, offre une interface complète : consultation et modification de tous les modèles de données, gestion des tarifs et des stocks, consultation des réservations et des paiements et production de rapports métier. Plus de 65 fichiers de schéma JSON, un par table, définissent les libellés des champs, leurs types, le tri et leur visibilité dans les listes. Ils alimentent un moteur dynamique de listes et de formulaires : ajouter un champ en base le fait apparaître automatiquement dans l’administration sans modifier les gabarits.


Quelques chiffres

  • 233 fichiers source PHP
  • Plus de 70 modèles de données couvrant toute l’activité
  • Plus de 65 tables de base de données
  • 50 classes de base du framework
  • 11 contrôleurs de pages publiques
  • 32 fichiers partiels SCSS compilés en un système de design cohérent
  • 26 modules JavaScript fonctionnels
  • 96 fichiers de classes 4D et 1 099 méthodes historiques dans l’application de bureau
  • 62 déclencheurs de base de données alimentant la synchronisation bidirectionnelle
  • Environ 20 postes de bureau exécutant l’application 4D dans plusieurs magasins d’Ameland
  • Deux passerelles de paiement, trois types de documents PDF et une API REST de suivi

Ce que représente ce projet

Kiewiet dépasse le simple site équipé d’un module de réservation. C’est une plateforme opérationnelle complète, développée selon les besoins précis d’une entreprise à forte activité saisonnière où la marge d’erreur est faible : un vélo réservé deux fois ou une livraison de bagages manquée affecte les vacances d’une famille. Chaque couche, de la table de routage au moteur de synchronisation et à l’optimisation des tournées, a été conçue pour cette réalité.

Les choix techniques — ORM sur mesure avec sommes de contrôle pour détecter les modifications, graphe d’objets de pages conservé en session, journal bidirectionnel entre deux environnements d’exécution hétérogènes — ne visaient pas l’élégance pour elle-même. Ils répondaient à un comportement précisément exigé par l’activité, qu’aucune solution prête à l’emploi ne proposait.

Images du projet

Autres réalisations

Projets similaires

Démarrez un projet avec nous

Vous avez un projet en tête ? Échangeons sur la manière dont nous pouvons vous aider.