Skip to content

Module product-catalog (mobile)

Ce fichier doit rester synchronisé avec le code du module. À mettre à jour à chaque changement structurel.

Rôle : référentiel des « produits » attachables à un voyage (hébergements, types de chambres, assurances, véhicules, lignes/arrêts). Le mobile en a besoin pendant le wizard de réservation pour proposer les choix et calculer les prix. Aligné sur le module backend Horizon product-catalog.

Code

  • Store : stores/catalog.ts (useCatalogStore)
    • Indexé par travelId (cache par voyage : loadedTravels, loadingTravels)
    • Maps : lines, stops, roomTypeCategories, accommodations, roomTypes, insurances, vehicles
    • L'option « Aucune assurance » est une Insurance réelle value 0 fournie par le backend dans catalog.insurances (plus de constante synthétique côté front depuis que la pension/assurance démarrent vierges — cf. business-rules.md).
    • seasideCategories (TravelCategory[]) : destinations balnéaires (données de référence quasi-statiques). Préchargé une fois au cold start via useCatalogStore(pinia).fetchSeasideCategories() appelé dans main.ts juste après app.use(pinia) (fire-and-forget) — donc une fois par (re)chargement de l'app, garanti même si aucun composant ne touche le store ; l'endpoint est lui-même LRU-caché 30 min. Erreur réseau logguée via logError(_, 'catalog'), ne bloque pas le démarrage. Les boutons de SeasideInterestsFilterBar correspondent à ces catégories (noms et icônes hardcodés pour l'instant — icônes SVG inline <InlineSVG :src="/svg-icons/..."> à la place des anciens drapeaux flag-icons, même pattern que InterestsFilterBar : recoloration à la sélection via currentColor) ; le filtre envoyé au backend est ?seasideCategoryName=Baln {NOM} (cf. travel-catalog.md).
  • Components : cards/AccommodationPreview.vue, travel-booking/TBStepAccommodation*.vue, travel-booking/TBStepDepartureLocation.vue
  • API : publicApi.travels.getCatalog(travelId)TravelCatalog (lines, roomTypeCategories, accommodations, insurances, vehicles), publicApi.travels.getSeasideCategories()TravelCategory[]
  • Utils :
    • accommodation-optimizer.tsparseDaysAccommodations(travel) : algo greedy Set Cover pour grouper les jours d'un voyage par hôtel (NP-Hard, approximation suffisante < 30 jours / < 10 hôtels)
    • meal-plans.ts — pensions (MealPlan enum)
    • room-keys.ts — sérialise (roomTypeId, roomId, optionId) en clé stable pour les Maps du wizard
    • age-tiers.ts — calcul du PassengerType (Adult/Junior/Child/Baby) selon l'âge et les seuils de l'Accommodation ou du Travel

Entités principales

  • Accommodation (api_Accommodation) — hôtel/logement. Porte ChildMin/Max, JuniorMin/Max, AdultMin (seuils d'âge), MealPlan par défaut, internal (true = négocié Buchard, sinon externe → ignoré par l'optimizer).
  • RoomType, RoomTypeCategory, Room, Option (RoomTypeOption)
  • SettingInsurance (alias CatalogInsurance) — assurance avec priceStart/priceEnd (tranche de prix par personne) et startDate/endDate (validité)
  • Vehicle, Seat
  • Line, Drive, Stop

Invariants & règles spécifiques

  • Accommodation optimizer : parseDaysAccommodations() ignore les hôtels externes (internal: false). Les jours sans hôtel ne sont dans aucun groupe. Pour Seaside, court-circuit : un seul hôtel travel.accommodation couvre 100% des jours.
  • Filtrage des assurances (getInsurancesForPricePerPerson) : double critère
    1. prix par personne dans [priceStart, priceEnd]
    2. date de l'occurrence (selectedTO.start) dans [startDate, endDate] de l'assurance Tri descendant par value.
  • requiresMealPlan : true uniquement quand le voyage est Seaside ET a une accommodation. Le vol-sec Seaside (sans hôtel) ne demande pas de pension.
  • Catalog chargé une fois par travelId : le store dédoublonne les appels concurrents via loadingTravels.

Dépendances

  • Dépend de : travel-catalog (chargé à partir d'un travelId)
  • Consommé par : booking (le wizard pioche tout le catalog ici), billing-payment (insurances pour le pricing)

Points d'attention

  • Toujours utiliser parseRoomKey() / les helpers de room-keys.ts pour manipuler les clés composites — ne pas concaténer à la main.
  • Les vehicles du catalog représentent les modèles de véhicule (capacité, plan de sièges) ; ne pas confondre avec l'Occurrence.vehicles ou le SeasideDate.Vehicles côté Horizon qui sont des affectations datées.
  • Le plan de sièges du wizard (TBSeatMap.vue) ne lit catalog.vehicles que pour les voyages single-day et Seaside. Pour les voyages multi-day (hors Seaside), il lit désormais bookingConstructor.vehicles — sourcé de l'affectation réelle de l'occurrence, pas du modèle générique du catalog, car la capacité/le plan de sièges peuvent différer par date. Pas via selectedTO.vehicles (bug backend sur GET /occurrences/:id, renvoie une liste de véhicules vide) : la donnée fiable est travel.occurrences[].vehicles, chargée via GET /travel/slug et retrouvée par id d'occurrence — cf. occurrence-capacity.md. catalog.getSeat/catalog.seats ont été supprimés (seul catalog.vehicles subsiste, pour ce fallback single-day/Seaside) — la résolution numéro-de-siège passe désormais par bookingConstructor.getSeatsForPassenger.

Contributors

No contributors

Changelog

No recent changes