🚚 VROOM ! — Spécifications Techniques

Documentation complète du système de gestion des livraisons Voyey — v3.1 — 18/05/2026

📋 Table des matières

1. Architecture générale

VROOM est une application web de gestion logistique pour les livraisons de colis en Guadeloupe. Elle gère le cycle complet : réception en stock → planification des tournées → livraison → historique.

┌─────────────────┐     FDW (next_server)     ┌─────────────────────┐
│  DB Next (prod)  │◄────────────────────────►│   Supabase (Voyey)   │
│  DigitalOcean    │    next_data schema       │   PostgreSQL         │
└─────────────────┘                            │                     │
                                               │  vr_parcels_cache   │
                                               │  vr_tours / v2      │
                                               │  vr_tours_meta      │
                                               │  vr_deliveries      │
                                               │  vr_comments        │
                                               │  vr_client_labels   │
                                               │  vr_gps_corrections │
                                               │  vr_trucks          │
                                               │  vr_truck_history   │
                                               │  vr_agents          │
                                               └──────────┬──────────┘
                                                          │ Supabase JS SDK
                                               ┌──────────▼──────────┐
                                               │   Frontend (Web)     │
                                               │   index.html         │
                                               │   vr-app.js          │
                                               │   vr-database.js     │
                                               │   vr-history.html/js │
                                               └─────────────────────┘
        

2. Stack technique

Frontend

LanguageVanilla JavaScript ES6
FrameworkAucun — modules IIFE
HTML/CSSHTML5, CSS3 Grid/Flexbox
CarteLeaflet.js 1.9.4 (OpenStreetMap)
Export PDFjsPDF (via window.open + print)
BuildAucun — imports directs

Backend

Base de donnéesSupabase (PostgreSQL 15)
Source donnéesFDW next_server → DB Next
AuthCustom (email + PIN 6 chiffres)
Hébergementvtt.xo.je — InfinityFree (Apache mutualisé, dépôt FTP). ⚠️ Migration vers voyey.com envisagée mais NON encore effectuée au 18/05/2026.
SyncCron pg_cron multi-paliers : full /15 min, light /5 min, images /15 min, purge /30 min (détail §19)
APISupabase REST (PostgREST)

3. Fichiers du projet

FichierTailleRôle
index.html280 KBUI principale — tous les onglets, modals, styles CSS
vr-app.js523 KBLogique métier — 12 000+ lignes, IIFE, 120+ fonctions publiques
vr-database.js29 KBCouche d'abstraction Supabase — 730 lignes, module IIFE VR_Database
vr-history.html19 KBUI historique — page séparée
vr-history.js58 KBLogique historique — 1 275 lignes, module IIFE VRHistory
sql/update_sync_parcels_cache.sql6 KBFonction RPC sync_parcels_cache() — synchronisation FDW
sql/vr_trucks.sql6.5 KBSchéma camions — tables + fonctions + vue

4. Base de données (Supabase)

4.1 vr_parcels_cache TABLE SYNC 30min

Cache local des colis synchronisé depuis la DB Next via FDW. Source de vérité pour l'app VR.

ColonneTypeDescription
idBIGINT PKID colis dans Next (logistics_parcel.id)
stickerVARCHARNuméro de sticker (numérique, ex: 46831)
typeVARCHARType de colis (L1, L2, XL, Pneu, etc.)
weightFLOATPoids en kg
client_emailVARCHAREmail du client (clé de regroupement)
client_nameVARCHARPrénom + Nom
client_phoneVARCHARTéléphone (+590...)
addressTEXTAdresse de livraison spécifique ou par défaut
villeVARCHARVille (depuis logistics_city ou adresse)
zip_codeINTCode postal (971XX)
lieu_ditTEXTLieu-dit / complément d'adresse
instructionTEXTInstructions de livraison
latitudeFLOATGPS latitude (nullable)
longitudeFLOATGPS longitude (nullable)
gps_certifiedBOOLEANGPS vérifié manuellement
zone_idINTID zone de livraison (FK logistics_zone)
zone_nameVARCHARNom de zone (B/T-Z1, G/T-Z2, Point relais Jarry...)
transaction_idBIGINTID transaction/container Next
transaction_nameVARCHARNom transaction (T0320, T0321...)
etagereVARCHARNuméro d'étagère (1-799 = stock, 800-899 = camions)
statusVARCHARStatut colis (voir §4.1.1)
order_valueDECIMALValeur déclarée de la commande
payment_statusVARCHARStatut paiement (PAID, PENDING...)
imagesJSONBPhotos du colis [{id, url, date}]
anomaly_flagsTEXT[]Flags d'anomalies détectées (voir §20)
parcel_created_atTIMESTAMPTZDate de création dans Next
synced_atTIMESTAMPTZDernière synchronisation

4.1.1 Statuts des colis (whitelist sync)

StatusSignificationDans le cache ?
PRET_RETRAITEn stock, prêt à retirer en point relais✅ Oui
PRET_POUR_PLANIFICATIONEn stock, à planifier en livraison✅ Oui
PRET_LIVRAISONPlanifié, prêt à livrer✅ Oui
PLANIFICATION_LIVRAISONEn cours de planification✅ Oui
PRET_RECUPERATIONPrêt à récupérer✅ Oui
STOCKEEn stock (pas encore prêt)✅ Oui
TRANSITEn transit France → Guadeloupe❌ Exclu
RECEPTIONNE_BOISSYReçu au dépôt France❌ Exclu
ENREGISTRE_VOYEYEnregistré mais pas prêt❌ Exclu
LIVRELivré❌ Exclu
PERDUPerdu❌ Exclu
ANNULEAnnulé❌ Exclu

4.2 Autres tables

TablePK / UniqueRôle
vr_tours(tour_number, tour_date)Tournées legacy — liste d'emails par tournée/date
vr_tours_v2id (UUID)Tournées v2 — clients_data (JSONB), status, planned_date
vr_tours_meta(tour_num, date)Métadonnées tournée — agent, véhicule, schedule, report, tracking
vr_deliveries(tour_number, tour_date, client_email)Statut livraison par client — delivered/absent/postponed/cancelled
vr_commentsid (auto)Commentaires clients — contenu, auteur, priorité, mentions
vr_client_labelsclient_emailLabels priorité — normal/high/urgent
vr_gps_correctionsclient_emailCorrections GPS manuelles — lat, lng
vr_trucksidConfig camions — driver, shelf_start..shelf_end, bags_count
vr_truck_historyidHistorique mouvements camion — loaded/unloaded/delivered
vr_agentsidListe des agents/livreurs
vr_notificationsidNotifications mentions @agent
vr_clients_geo_captureidGPS capturés pendant livraison

5. FDW & Synchronisation

Serveur FDW

ParamValeur
Nomnext_server
Wrapperpostgres_fdw
Hostdb-postgresql-voyey-production-db-do-user-4749825-0.m.db.ondigitalocean.com
Port25060
Databasevoyeyproduction
Remote useralexandre
Schéma localnext_data

Tables distantes (next_data.*)

logistics_parcel, logistics_shelf, logistics_zone, logistics_city, logistics_transaction, logistics_orderrequest, logistics_parcelimage, userzone_customer, userzone_customeraddress, userzone_user, payment_payment

Fonction sync_parcels_cache() RPC CRON multi-paliers

Appelée par sync_parcels_full_safe() via pg_cron job sync-parcels-full-15min (5,20,35,50 * * * *, timeout 600s). Sync incrémentale légère en complément via sync_parcels_light() (*/5). Voir §19.

1
Crée table temporaire _protected_stickers depuis les tournées actives (draft, ready, in_progress, paused) + completed < 24h
2
DELETE du cache : supprime les colis non protégés qui n'existent plus dans Next avec un statut éligible
3
UPSERT : insère/met à jour tous les colis éligibles depuis next_data avec jointures adresse (spécifique + fallback default), zone, ville, transaction, étagère
4
Nettoyage table temporaire
Filtres critiques dans le WHERE :
p.is_deleted = false
p.sticker IS NOT NULL (exclut colis sans sticker)
p.status IN ('PRET_LIVRAISON', 'PLANIFICATION_LIVRAISON', 'PRET_POUR_PLANIFICATION', 'PRET_RETRAIT', 'PRET_RECUPERATION', 'STOCKE')
p.is_delivered = false OR sticker protégé

Fonction sync_parcel_images() RPC

Met à jour la colonne images (JSONB) depuis next_data.logistics_parcelimage. Appel manuel.

6. vr-database.js — Module DB

Module IIFE VR_Database. Couche d'abstraction Supabase. Toutes les fonctions sont asynchrones sauf init(), isTruckShelf() et convertToClients().

Connexion

FonctionRetourDescription
init()booleanInitialise le client Supabase. Doit être appelé avant toute opération.
getClient()SupabaseClientRetourne l'instance Supabase brute
syncCache()string|nullAppelle RPC sync_parcels_cache()
getLastSyncDate()string|nullRetourne le synced_at le plus récent

Colis & Clients

FonctionRetourDescription
getAllParcels(excludeTrucks=false)Parcel[]Charge tous les colis. Exclut automatiquement les anomalies (sans étagère sauf PRET_LIVRAISON/PLANIFICATION_LIVRAISON). Option pour exclure les camions (800-899).
getCacheStats()Stats{}Statistiques agrégées : total, par zone, par ville, par transaction, livraison vs retrait
convertToClients(parcels)Client[]Regroupe les colis par email client. Détermine le type (livraison/retrait), corrige zones et villes via code postal, marque les colis en camion.

Tournées

FonctionRetourDescription
saveTour(n, emails)booleanUPSERT vr_tours (legacy) — tour_number + tour_date du jour
loadTours()Tour[]Charge les tournées à partir d'aujourd'hui

Commentaires

FonctionRetourDescription
loadComments(email?)Comment[]Charge les commentaires. Si email fourni, filtre par client.
addComment(email, content, author, priority)booleanAjoute un commentaire. Priority : normal | high | urgent
loadCommentsStats(){email: {count, maxPriority}}Stats pour badges dans la liste clients

Labels, GPS, Livraisons, Agents

FonctionRetourDescription
loadLabels()Label[]Charge les labels de priorité client
saveLabel(email, priority)booleanUPSERT ou DELETE si priority=normal
loadGPSCorrections()GPS[]Charge les corrections GPS manuelles
saveGPSCorrection(email, lat, lng)booleanUPSERT correction GPS
loadDeliveries(tourNum, date)Delivery[]Charge les livraisons pour une tournée/date
saveDelivery(tourNum, date, email, status, comment)booleanUPSERT statut de livraison
loadAgents()Agent[]Charge la liste des agents/livreurs

Camions

FonctionRetourDescription
isTruckShelf(shelf)booleanVérifie si étagère entre 800-899
loadTrucks()Truck[]Config camions actifs (driver, shelves, bags)
getTruckParcels(){trucks, totalParcels}Colis groupés par camion avec main/bags
updateTruck(truckId, updates)TruckMet à jour la config d'un camion
logTruckAction(truckId, parcelId, sticker, action, from, to, userId, userName, notes)HistoryEnregistre un mouvement (loaded, unloaded, moved_to_bag, delivered)

Constantes exportées

ZONES_LIVRAISON = ['B/T-Z1', 'B/T-Z2', 'B/T-Z3', 'G/T-Z1', 'G/T-Z2', 'G/T-Z3']
ZONES_RETRAIT   = ['Point relais Jarry']
TRUCK_SHELF_MIN = 800
TRUCK_SHELF_MAX = 899

7. vr-app.js — Application principale

Module IIFE. 12 000+ lignes. Gère toutes les vues, interactions, filtres et persistance.

Fonctions utilitaires critiques

FonctionRetourDescription
getLocalDateStr(date?)string YYYY-MM-DDTimezone-safe. Retourne la date locale. Accepte Date, string ISO, YYYY-MM-DD ou vide. Remplace toISOString().split('T')[0].
formatDateFr(dateStr)stringTimezone-safe. "mer. 27 mars". Utilise new Date(y, m-1, d, 12) en heure locale.
getTourDisplayNum(tourNum)numberRetourne le vrai numéro de tournée (identité). Les couleurs sont gérées séparément par getTourColor().
getTourColor(tourNum)string CSSCouleur basée sur l'index dans activeTourNumbers. Fallback : hsl((num*137)%360, 70%, 50%)
getTourKey(tourNum, date)stringClé composite ${date}-T${tourNum} pour indexer toursMeta

Onglets principaux (10 vues)

Vuedata-viewDescription
📊 DashboarddashboardKPIs : colis total, clients, GPS %, zones, transactions, colis lourds/valeur
📦 TransactionstransactionsVue par transaction/container. Filtrage, assignation par lot.
👥 ClientsclientsListe complète. Filtres zone/transaction/GPS. Sidebar livraison/retrait/mixte.
🗺️ CartemapCarte Leaflet interactive. Markers par zone/tournée. Popups client.
🚚 TournéestoursGrille tournées par ville/zone. Cards avec statut, clients, GPS. Planification.
🚛 CamionstrucksVue camions. Étagères 800-899. Main + bags (grille 3x3). Colis par compartiment.
📋 LivraisondeliveryInterface livraison active. Client courant, GPS, timer, pause, commentaire.
👔 ManagermanagerSupervision temps réel. Stats par tournée. Suivi agents. Config camions.
📜 HistoriquehistoryLien vers vr-history.html (page séparée)
⚠️ AnomaliesanomaliesLien vers vr-anomalies.html (page séparée, à créer)

État interne principal

parcels[]       // Colis bruts depuis vr_parcels_cache
clients[]       // Clients groupés (output de convertToClients)
stats{}         // Statistiques agrégées du cache
tours{}         // {tourNum: [clientIds]} — assignations locales
toursMeta{}     // {tourKey: {id, status, date, agent, schedule, report, tracking}}
tourIdCache{}   // {tourNum: UUID} — mapping vers vr_tours_v2
agents[]        // Liste agents/livreurs
currentTour     // Tournée active sélectionnée
currentUser     // {id, email, name, role, sector}
map             // Instance Leaflet
markers[]       // Markers Leaflet sur la carte
clientComments{}// Cache commentaires {email: {count, maxPriority}}
clientLabels{}  // Cache labels {email: {priority}}

8. Zones de livraison

ZoneCouleurTypeCommunes
B/T-Z1RougeLivraisonLamentin (97129), Sainte-Rose (97115), Deshaies (97126), Pointe-Noire (97116)
B/T-Z2JauneLivraisonBaie-Mahault (97122), Petit-Bourg (97170)
B/T-Z3VertLivraisonCapesterre-BE (97130), Trois-Rivières (97114), Basse-Terre (97100), +9 communes
G/T-Z1BleuLivraisonMorne-à-l'Eau (97111), Petit-Canal (97131), Port-Louis (97117), Anse-Bertrand (97121), Le Moule (97160)
G/T-Z2NoirLivraisonLes Abymes (97139/42/83), Pointe-à-Pitre (97110)
G/T-Z3OrangeLivraisonLe Gosier (97190), Sainte-Anne (97180), Saint-François (97118), La Désirade, Marie-Galante
Point relais JarryVioletRetraitRetrait en point relais — pas de GPS, pas d'adresse
Correction automatique : Si un colis livraison n'a pas de zone valide, le code JS détermine la zone à partir du code postal (CP_TO_ZONE). Idem pour la ville (CP_TO_CITY).

9. Gestion des tournées

Cycle de vie d'une tournée

  draft ──► planned ──► in_progress ──► completed ──► archived
    │                       │
    │                       ▼
    │                    paused ──► in_progress (reprise)
    │
    └── deleted (si vide)
        

Structure d'une tournée (vr_tours_meta)

{
  tour_num: 55,                    // Numéro (55-99+)
  date: '2026-03-24',              // Date planifiée
  status: 'planned',               // Statut courant
  agent: { id, name, email },      // Agent/livreur assigné
  driver_name: 'Melinda',
  vehicle: { type, plate },        // Véhicule
  schedule: {                      // Horaires prévus
    journeyStart, journeyEnd,
    tourStart, tourEnd,
    pauseDuration
  },
  tracking: {                      // Données de suivi temps réel
    startTime, endTime,
    deliveryLogs: [{ clientId, timestamp, status, gps }],
    pauses: [{ start, end, duration }],
    totalPauseTime
  },
  report: {                        // Rapport de fin de tournée
    fuelLevel, coolantOk,
    expenses: [],
    issues, urgentReschedule,
    cancelledClients: [],
    validated: true/false
  }
}

Assignation des clients

Les clients sont assignés par zone, par ville, individuellement ou par transfert depuis une autre tournée. L'ordre peut être modifié par drag & drop. Les données sont persistées dans vr_tours_v2.clients_data (JSONB).

Numérotation

Tournées numérotées de 55 à 99+. Jusqu'à 12 tournées ont des couleurs prédéfinies (voir CSS). Au-delà, couleur calculée par hsl((num * 137) % 360, 70%, 50%).

10. Système de livraison

Statuts de livraison par client

StatutIcôneSignification
pendingPas encore traité
deliveredLivré avec succès
absent🚫Client absent
postponed📅Reporté à une date ultérieure
cancelledAnnulé

Persistance offline-first

L'état de livraison est sauvé en localStorage à chaque action. En cas de perte réseau, le livreur peut continuer. La synchronisation Supabase est throttled à 2 secondes.

Tracking temps réel

Timer de livraison avec gestion des pauses. Chaque livraison enregistre un log avec timestamp, coordonnées GPS, et durée depuis le client précédent.

11. Gestion des camions

Convention étagères

RangeUsage
1 — 799Stock entrepôt (étagères physiques)
800 — 899Réservé camions

Structure camion

Exemple : Camion de Melinda
  shelf_start: 800, shelf_end: 809
  truck_shelf: 800 (compartiment principal)
  bags_count: 9 (bags 801-809)

  Visualisation grille 3×3 :
  ┌─────┬─────┬─────┐
  │ 801 │ 802 │ 803 │
  ├─────┼─────┼─────┤
  │ 804 │ 805 │ 806 │
  ├─────┼─────┼─────┤
  │ 807 │ 808 │ 809 │
  └─────┴─────┴─────┘
  + Compartiment principal : 800

Les colis sur étagère 800-899 sont exclus du stock actif (sauf si excludeTrucks=false) mais visibles dans l'onglet Camions.

12. GPS & Carte

Sources GPS (par ordre de priorité)

1
vr_gps_corrections — corrections manuelles (priorité max)
2
delivery_address.latitude/longitude — adresse de livraison spécifique
3
default_address.latitude/longitude — adresse par défaut du client
4
captureClientGeolocation() — capture GPS navigateur pendant livraison

Configuration carte

ParamValeur
Centre[16.265, -61.551] (Guadeloupe)
Zoom11
Bounds Guadeloupe15.8°N — 16.6°N / -62° — -60.8°W
Timeout géoloc15 secondes
ProviderOpenStreetMap via Leaflet.js 1.9.4

Les clients retrait n'ont PAS de GPS (ils viennent chercher au point relais). Les markers sont colorés par tournée ou par zone selon le mode.

13. Commentaires & Mentions

Système de commentaires

Chaque client peut avoir des commentaires avec 3 niveaux de priorité :

PrioritéAffichage
normalTexte standard
high⚠️ Badge orange
urgent🚨 Badge rouge

Mentions @agent

Taper @ dans un commentaire active l'autocomplétion des noms d'agents (vr_agents). Les mentions créent des notifications (vr_notifications).

14. Labels & Priorités clients

Table vr_client_labels. Un label par client (UPSERT sur client_email). Affichage visuel dans la liste clients et la carte.

PrioritéCouleurEffet
normalPas de label (DELETE de la table)
highOrange/Jaune⚠️ Highlight dans la liste
urgentRouge🚨 Highlight + priorité visuelle haute

15. vr-history.js — Module historique

Page séparée (vr-history.html). Module IIFE VRHistory. Affiche les tournées terminées/archivées.

Sources de données

1
vr_tours_meta — 200 dernières entrées (agent, tracking, schedule, report)
2
vr_deliveries — statuts livraison par client (batchés par 10 dates)
3
vr_tours_v2 — statuts et comptages clients

Filtres disponibles

Période (aujourd'hui / semaine / mois), numéro de tournée, nom d'agent.

Vue détail

Chaque tournée affiche : agent, véhicule, horaires (prévus vs réels), résultats (livrés/absents/reportés/annulés), pauses, chronologie des livraisons, dépenses, notes, export PDF.

Export PDF

Génère un document HTML imprimable (window.open + print) avec tableau complet des livraisons, statistiques et notes.

16. Authentification & Rôles

Flux de connexion

1
L'utilisateur entre son email + PIN à 6 chiffres
2
Vérification contre la table users (email + pin_code)
3
Session sauvée en localStorage['vr_user'] + localStorage['vttUser']

Rôles

RôleAccès
agent (livreur)Dashboard, Clients, Carte, Tournées, Livraison, Camions, Historique
managerTout + onglet Manager (supervision, config camions)
adminTout
Pas de Supabase Auth. L'authentification est custom (table users + PIN). Pas de JWT Supabase. La clé anon_key est utilisée directement.

17. Flux de données

Pipeline de chargement principal

init()
  └─► VR_Database.init()
       └─► loadData()
            ├─► getAllParcels()            // vr_parcels_cache (filtre anomalies)
            ├─► convertToClients(parcels)  // Regroupement par email
            ├─► getCacheStats()            // Agrégation zones/transactions
            ├─► applyGPSCorrections()      // vr_gps_corrections
            ├─► loadSavedTours()           // vr_tours (today)
            ├─► loadCommentsIndicators()   // vr_comments stats
            ├─► loadClientLabels()         // vr_client_labels
            └─► Render : Dashboard + Filtres + Grille tournées + Carte

Détermination type client (livraison vs retrait)

Pour chaque colis :
  1. STATUS prioritaire :
     - PRET_RETRAIT / PRET_RECUPERATION → retrait
     - PRET_LIVRAISON / PLANIFICATION_LIVRAISON / PRET_POUR_PLANIFICATION → livraison
  2. Sinon, ZONE :
     - B/T-Z* ou G/T-Z* → livraison
     - "relais" ou "jarry" ou vide → retrait
  3. Fallback : retrait

Pour le client (après regroupement) :
  - Si AU MOINS UN colis livraison → clientType = 'livraison'
  - Sinon → clientType = 'retrait'
  - Retrait : zone = 'Point relais Jarry', GPS = null, adresse = null

18. Constantes & Configuration

ConstanteValeurUsage
SUPABASE_URLhttps://qkxggtrmpgqjbwlaoztn.supabase.coEndpoint Supabase
TRUCK_SHELF_MIN/MAX800 / 899Range étagères camions
DEFAULT_MAP_CENTER[16.265, -61.551]Centre carte Guadeloupe
DEFAULT_ZOOM11Zoom initial carte
GEOLOC_TIMEOUT15 000 msTimeout capture GPS navigateur
SYNC_THROTTLE2 000 msDélai sync Supabase pendant livraison
HEAVY_PARCEL20 kgSeuil colis lourd
HIGH_VALUE500 €Seuil haute valeur
TOUR_NUM_RANGE55 — 99+Numéros de tournée
WORKING_DAYSLundi — SamediPas de tournée le dimanche

19. Cron Jobs (pg_cron)

Job IDScheduleFonctionDescription
sync-parcels-full-15min5,20,35,50 * * * *sync_parcels_full_safe()Sync complète cache colis depuis Next → vr_parcels_cache (timeout 600s)
sync-parcels-light-5min*/5 * * * *sync_parcels_light()Sync incrémentale légère (changements récents, timeout 300s)
sync-parcels-images-15min13,28,43,58 * * * *sync_parcel_images_new()Sync nouvelles photos colis (timeout 300s)
sync-parcels-purge-30min12,42 * * * *sync_parcels_purge()Purge colis disparus de Next (timeout 300s)
30 6 * * *cron_wrapper_close_forgotten()Ferme les sessions VTT oubliées
40 5 * * *cron_wrapper_cleanup_tokens()Nettoyage tokens expirés
50 7 * * *cron_wrapper_integrity_check()Vérification intégrité VTT
60 22 * * *cron_wrapper_anomalies_check()Détection anomalies VTT

20. Anomalies & Maintenance

Types d'anomalies détectées

CodeDétectionGravité
PRET_SANS_ETAGEREstatus PRET_* mais etagere vide/nullHaute
CLIENT_NON_IDENTIFIEclient_email et client_name vides ou "NON IDENTIFIÉ"Haute
SANS_STICKERsticker NULL (filtré à la sync, ne devrait plus arriver)Haute
ANCIEN_STOCKparcel_created_at > 6 moisMoyenne
LIVRAISON_SANS_ZONEstatus livraison mais zone_name videBasse

Filtrage dans l'app principale

Les colis anomalies (sans étagère, hors PRET_LIVRAISON/PLANIFICATION_LIVRAISON) sont automatiquement exclus de getAllParcels() et getCacheStats(). Ils restent dans vr_parcels_cache et sont accessibles via la page Anomalies.

Colonne anomaly_flags (TEXT[]) : Ajoutée le 19/03/2026. Remplie par la sync (step post-upsert). Indexée GIN pour requêtes rapides.

Historique des corrections (19/03/2026)

ProblèmeCorrection
FDW voyey_next_server cassé (hostname introuvable)Migration vers next_server + suppression ancien FDW
Filtre blacklist (laissait passer ENREGISTRE_VOYEY, PRET_VWAZINAJ)Remplacé par whitelist stricte
Zones vides (zone_id/zone_name toujours NULL)Restauration jointures zone dans la sync
Adresses fausses (uniquement default_address)Restauration delivery_address_id + fallback default
984 colis sticker NULL dans le cacheAjout filtre sticker IS NOT NULL
TRANSIT + RECEPTIONNE_BOISSY dans le cache (pas en stock physique)Retirés de la whitelist de sync
sync_parcel_images() casséeMigrée de voyey_next vers next_data

Corrections planification tournées (26/03/2026)

ProblèmeCause racineCorrection
Dates décalées d'un jour (27 mars affiché 26 mars) Bug timezone : new Date().toISOString().split('T')[0] retourne UTC (en Guadeloupe UTC-4, minuit local = veille UTC). formatDateFr() utilisait new Date(str + 'T00:00:00') interprété en UTC. Ajout de getLocalDateStr(date) : utilitaire timezone-safe basé sur getFullYear()/getMonth()/getDate(). Remplacement de ~30 occurrences. formatDateFr() utilise new Date(y, m-1, d, 12, 0, 0) (heure locale midi).
Numérotation incohérente (T3 à côté de T188, T203) getTourDisplayNum() convertissait le numéro réel en index dans activeTourNumbers (T188 → T1 si position 0) getTourDisplayNum() retourne maintenant le vrai numéro. Les couleurs restent basées sur l'index pour la cohérence visuelle.
Tournées affichées avec 0 clients alors qu'elles en ont Clé composite ${date}-T${tourNum} ne matchait pas entre vr_tours_meta et vr_tours_v2 car meta.date contenait un timestamp complet au lieu de YYYY-MM-DD meta.date est maintenant toujours normalisé en YYYY-MM-DD dans loadToursMeta()
Données différentes selon le clic (carte "À venir" vs "Prochaines tournées") openTourDetailModal ne chargeait que depuis toursMeta en mémoire (vide si le match échouait). openTourPlanModal avait des fallbacks multiples. openTourDetailModal est maintenant async avec fallback direct vers vr_tours_v2 quand clientsData est vide. Le résultat est mis en cache dans meta.
Numéros de tournées bloqués entre jours (T1 du jour bloquait T1 de demain → création de T188, T203) getAvailableTourNumbersForDate() excluait les numéros utilisés aujourd'hui (blockedByToday) même pour les dates futures Les numéros sont maintenant indépendants par jour. T1 du 26/03 ≠ T1 du 27/03. Propose T1 à T20 en excluant seulement ceux déjà utilisés pour CE jour.
create_tour_v2 RPC appelé sans p_tour_number → auto-incrément global (T205, T206...) 3 call sites sur 6 ne passaient pas p_tour_number à la RPC. get_next_tour_number() générait des numéros séquentiels globaux. Tous les appels à create_tour_v2 passent maintenant p_tour_number: tourNum. Plus d'auto-incrément orphelin.
Données orphelines dans vr_tours_v2 (T205-T212 sans correspondance meta) Conséquence des 2 bugs ci-dessus : meta avait T3/T188/T203, v2 avait T205-T212 Nettoyage manuel : orphelins supprimés, T208→T1, T209→T2, T206→T3. Meta réaligné avec tour_key correct.

Fonctions utilitaires ajoutées / modifiées (26/03/2026)

// Obtenir la date locale au format YYYY-MM-DD (timezone-safe)
getLocalDateStr(date?)
  Input: Date object, string ISO, string YYYY-MM-DD, ou vide (= maintenant)
  Output: string 'YYYY-MM-DD' en heure LOCALE (pas UTC)
  Usage: Remplace new Date().toISOString().split('T')[0] partout (~30 occurrences)

// Formater une date en français (timezone-safe)
formatDateFr(dateStr)
  Input: string date (tout format)
  Output: 'mer. 27 mars' (fr-FR, weekday short + day + month short)
  Logique: Parse YYYY-MM-DD → new Date(y, m-1, d, 12, 0, 0) en LOCAL

// Numéros de tournée disponibles pour une date (indépendants par jour)
getAvailableTourNumbersForDate(dateStr, excludeNum?)
  Propose T1-T20, exclut seulement les numéros déjà utilisés POUR CE JOUR
  Plus de blocage cross-jours (T1 du 26 n'empêche plus T1 du 27)

// Tour ID Cache — isolation par jour (fix 30/03/2026)
getTourCacheKey(tourNum, date?)
  Output: "tourNum-YYYY-MM-DD" (ex: "1-2026-03-31")

getCachedTourId(tourNum, date?)
  Lit tourIdCache[getTourCacheKey(tourNum, date)]

setCachedTourId(tourNum, date, tourId)
  Écrit tourIdCache[getTourCacheKey(tourNum, date)] = tourId

Règles de gestion des numéros de tournée

Numéros par jour : Les tournées sont numérotées T1, T2, T3... par date. Chaque jour repart de T1. Deux jours différents peuvent avoir chacun une T1 — elles sont distinguées par la clé composite ${date}-T${tourNum}.
TableClé d'unicitéExemple
vr_tours_metatour_key = ${date}-T${tourNum}2026-03-27-T1
vr_tours_v2(tour_number, planned_date)tour_number=1, planned_date=2026-03-27
tourIdCache (JS)${tourNum}-${date}1-2026-03-27

La RPC create_tour_v2 accepte p_tour_number (obligatoire côté JS). Si le numéro existe déjà pour cette date, elle lève une exception. Le fallback get_next_tour_number() (auto-incrément global) ne doit jamais être utilisé — tous les appels JS passent maintenant le numéro explicitement.

tourIdCache — Piège corrigé le 30/03/2026 : Le cache était indexé par tourNum seul (ex: tourIdCache[1]). Quand T1 du 30/03 était en cache, T1 du 31/03 réutilisait le même UUID → clients sauvés dans la mauvaise tournée + création d'orphelins auto-incrémentés (T218-T221). Fix : 21 occurrences migrées vers getCachedTourId(tourNum, date) / setCachedTourId(tourNum, date, tourId).

21. Historique complet des corrections

18/05/2026 — Mise à jour doc (transmission Dannick) + intégration dashboard VIE

PointÉtat réel (vérifié PROD)
Hébergement frontend (doc disait « voyey.com »)FAUX : hébergé sur vtt.xo.je (InfinityFree, Apache mutualisé, dépôt FTP). Migration voyey.com non effectuée.
Cron sync colis (doc disait « toutes les 30 min »)OBSOLÈTE : multi-paliers — full /15 min (sync_parcels_full_safe), light /5 min (sync_parcels_light), images /15 min, purge /30 min. Voir §19.
Intégration VIE Dashboard (mig 248)VROOM alimente le dashboard VIE : métrique vr.planned_tours_today (tournées du jour : agent, clients, colis, villes) + vr.parcels_by_status (bucket PRET_RETRAIT ajouté) + global.deliveries_today (planned_tours/clients). Source : vr_tours_metavr_tours_v2.
Piège data vr_tours_meta.agent / .zonesjsonb double-encodé (chaîne JSON, jsonb_typeof='string'). agent->>'name' = NULL. Décoder : (agent #>> '{}')::jsonb ->> 'name'.

19/03/2026 — Chantier FDW & Cache colis

ProblèmeCorrection
FDW voyey_next_server cassé (hostname introuvable après changement credentials)Migration de toutes les fonctions vers next_server + suppression du FDW cassé et du schéma voyey_next
Filtre blacklist dans sync_parcels_cache() (laissait passer ENREGISTRE_VOYEY, PRET_VWAZINAJ = 3 947 fantômes)Remplacement par whitelist stricte : PRET_LIVRAISON, PLANIFICATION_LIVRAISON, PRET_POUR_PLANIFICATION, PRET_RETRAIT, PRET_RECUPERATION, STOCKE
Zones vides (zone_id/zone_name toujours NULL dans le cache)Restauration des jointures zone dans la sync via delivery_zone_id + fallback customer.delivery_zone_id
Adresses fausses (uniquement default_address au lieu de delivery_address)Restauration delivery_address_id + fallback default. ALTER FOREIGN TABLE pour ajouter la colonne manquante.
984 colis sticker NULL dans le cache (non actionnables)Ajout filtre p.sticker IS NOT NULL dans le WHERE de la sync
TRANSIT + RECEPTIONNE_BOISSY dans le cache (pas en stock physique GP)Retirés de la whitelist. Le cache ne contient que les colis physiquement en stock.
sync_parcel_images() cassée (utilisait voyey_next)Migrée vers next_data.logistics_parcelimage
224 colis PRET_* sans étagère (anomalies source Next)Exclus du stock actif dans getAllParcels(). Restent dans le cache pour la page Anomalies.

26/03/2026 — Chantier Tournées (dates, numérotation, modals)

ProblèmeCorrection
Décalage de date -1 jour (tournée du 27 affichée comme 26)getLocalDateStr() : timezone-safe, remplace ~30 occurrences de toISOString().split('T')[0]
Numérotation incohérente (T3 à côté de T188, T203)getTourDisplayNum() retourne le vrai numéro (plus de conversion en index)
Numéros bloqués entre jours (T1 du 26 empêchait T1 du 27)getAvailableTourNumbersForDate() : propose T1-T20 par jour, indépendant
create_tour_v2 appelé sans p_tour_number → auto-incrément (T205-T212)5 call sites corrigés : tous passent p_tour_number: tourNum
Tournées 0 clients (meta↔v2 non liées)meta.date normalisé YYYY-MM-DD, openTourDetailModal async + fallback DB
Données différentes selon le modal cliquéUnification : même source de données (v2 avec fallback) quel que soit le point d'entrée

30/03/2026 — Chantier tourIdCache (doublons cross-jours)

ProblèmeCorrection
tourIdCache[tourNum] indexé par numéro seul → T1 du 30/03 réutilisait l'UUID de T1 du 29/03Cache migré vers tourIdCache["tourNum-date"]. 21 occurrences remplacées par getCachedTourId() / setCachedTourId().
Orphelins v2 créés par le bug (T218-T221)Nettoyage manuel : orphelins supprimés, tour_number réalignés avec meta
Agent/livreur perdu au changement de jourConséquence du match meta↔v2 cassé. Résolu par les fixes date + cache.

22. Page livraison autonome (vr-delivery)

30/03/2026 — Création et déploiement

AspectAncien (onglet dans vr-app.js)Nouveau (page autonome)
ArchitectureOnglet dans index.html, dépend de vr-app.js (12K lignes)Page séparée vr-delivery.html + vr-delivery.js (1 600 lignes). Utilise vr-database.js pour Supabase.
Taille chargée~550 KB (vr-app.js complet)~90 KB (vr-delivery.js + vr-database.js)
PersistancelocalStorage throttled 2s, sync silencieuseSave immédiat à chaque action + sync queue avec retry exponentiel (1s→60s, max 5 retries)
beforeunloadAucunAlerte si tournée en cours
Indicateur réseauAucunBadge vert (sync OK) / orange (pending) / rouge (offline)
Timer saveToutes les 30sToutes les 5s
GPS captureDB seulementlocalStorage + queue sync (double sécurité)
RapportDépend de clients[] en mémoireAutonome, flush sync avant archivage, fallback queue si DB fail
AuthPartagée avec vr-app.jsAutonome (même table users, même localStorage vr_user)
Page refreshRestauration partielle via vr-app.jsRestauration complète depuis localStorage (clients, statuts, timer, tracking)

Fichiers créés

FichierTailleRôle
vr-delivery.html~15 KBPage mobile-first pour livreurs terrain. CSS inline, bottom-sheet modals, safe-area iPhone.
vr-delivery.js~60 KBModule IIFE VR_Delivery. Auth, tours, delivery, timer, GPS, rapport, sync engine.

Architecture vr-delivery.js

VR_Delivery (IIFE)
├── Auth: checkSession(), login(), logout(), _doLogin()
├── Date helpers: getLocalDateStr(), formatDateFr(), formatTime(), formatDuration()
├── Tour loading: loadMyTours() → vr_tours_meta + vr_tours_v2
├── Persistence: saveStateLocal(), loadStateLocal(), clearStateLocal()
│   └── Key: 'vr_delivery_state_v2' (date-validated, full client data)
├── Sync engine: addToSyncQueue(), processSyncQueue(), updateSyncIndicator()
│   ├── Types: delivery, tracking, meta, gps
│   ├── Queue key: 'vr_sync_queue' (localStorage)
│   ├── Retry: exponential backoff [1s, 3s, 10s, 30s, 60s], max 5
│   └── Network: online/offline listeners auto-flush
├── Delivery: selectTour(), markClientStatus(), openDeliveryModal()
├── Timer: toggleTimer(), startTimerTick(), updateTimerDisplay(), updateTimerUI()
│   └── States: not started → running → paused → running → ...
├── GPS: captureGPS() — Guadeloupe bounds, localStorage + sync queue
├── Report: openReportModal(), collectReportData(), saveReportDraft(), validateReport()
├── UI: renderTourSelector(), renderDeliveryList(), updateProgress()
└── beforeunload: force save on exit

Sync Engine — détail

Offline-first : Chaque action de livraison est sauvée en localStorage immédiatement (synchrone), puis ajoutée à la sync queue pour Supabase. La queue est persistée en localStorage et traitée toutes les 3 secondes. Si une opération échoue, elle est retentée avec backoff exponentiel.
OpérationTable SupabaseMéthode
deliveryvr_deliveriesUPSERT (onConflict: tour_number, tour_date, client_email)
trackingvr_tours_metaUPSERT tracking JSONB
metavr_tours_metaUPSERT status + report
gpsvr_clients_geo_captureINSERT

Bugs corrigés pendant le déploiement

BugCauseFix
Login bouton inactifHTML statique avait onclick="VR_Delivery.login()" mais le JS expose _doLogin()Retiré le HTML statique login, le JS génère son propre overlay
Login "Email non trouvé"JS sélectait pin_code (n'existe pas), colonne réelle = pinpin_codepin dans select + comparaison
Login "Email non trouvé" (2)JS ne filtrait pas is_active = trueAjout .eq('is_active', true) + .select('*')
Tournées non chargéesJS sélectait agent_name (n'existe pas dans vr_tours_v2)Colonne retirée du select
Liste clients vide au clicHTML avait id="clientList", JS cherchait id="deliveryList"Aligné HTML sur JS
Bouton Démarrer/Terminer absentupdateTimerUI() ne gérait pas la visibilité de timerBar ni finishBtnAjout toggle visible class + gestion finishBtn
IDs progrès manquantsHTML manquait progressBar, progressPct, progressText, deliveryStatsIDs ajoutés dans le HTML

23. Gestion des colis par client (livraison partielle)

Ajouté le 31/03/2026. Permet au livreur de signaler quels colis ont été remis et lesquels ne l'ont pas été chez un même client.

Flux utilisateur

1
Le livreur ouvre la fiche d'un client — les colis s'affichent avec des checkboxes (tous cochés par défaut)
2
S'il ne peut pas remettre un colis → il le décoche
3
Il clique "Livré" → les stickers cochés vont dans parcels_delivered, les décochés dans parcels_not_delivered
4
Le commentaire de livraison inclut automatiquement "Colis non livrés: 129280, 123735"

Persistance (3 niveaux)

NiveauStockageDonnées
localStoragevr_delivery_state_v2deliveryStatuses[id].parcels_delivered + parcels_not_delivered (arrays de stickers)
Supabasevr_deliveriesColonnes JSONB parcels_delivered + parcels_not_delivered
TrackingtourTracking.deliveryLogs[]parcelsDelivered + parcelsNotDelivered arrays par log

Schema DB

ALTER TABLE vr_deliveries
  ADD COLUMN parcels_delivered JSONB DEFAULT '[]'::jsonb,
  ADD COLUMN parcels_not_delivered JSONB DEFAULT '[]'::jsonb;

-- Format: ["129280", "123735"]
-- Index pour requêtes sur colis non livrés
CREATE INDEX idx_deliveries_not_delivered
  ON vr_deliveries USING gin(parcels_not_delivered)
  WHERE parcels_not_delivered != '[]'::jsonb;

Rapport de fin de tournée

La section "Colis retour" du rapport affiche 2 catégories :

CatégorieBadgeSource
Colis de clients non livrés (absent/reporté/annulé)Absent / Reporté / AnnuléTous les colis du client sont retournés
Colis non remis chez un client livré (partiel)Non remisparcels_not_delivered du deliveryStatuses

UI — Modal livraison (stickers)

Les stickers sont affichés en gros, monospace, couleur or (#f0b009) avec checkbox. Bouton "Tout cocher/décocher" en haut de la liste. Les colis non cochés sont automatiquement ajoutés au commentaire ET aux champs JSONB dédiés.

Enrichissement des téléphones

Les clients_data de vr_tours_v2 ne contiennent pas le champ phone. Au chargement d'une tournée dans vr-delivery.js, les téléphones sont enrichis depuis vr_parcels_cache.client_phone par batch (requête .in('client_email', emails)).

Drag & Drop (réordonnement clients)

Les clients de la tournée peuvent être réordonnés par drag & drop (desktop + touch mobile). La technique utilisée est la même que dans vr-app.js (modale détail tournée) : déplacement DOM direct pendant le dragover/touchmove via insertBefore + getDragAfterElement(). Le nouvel ordre est sauvé en localStorage + sync vers vr_tours_v2.clients_data.

24. Panneau clients spéciaux (Tournées)

Ajouté le 31/03/2026. Panneau en haut de l'onglet Tournées affichant les clients nécessitant un traitement particulier. Tout fermé par défaut (toggle ▶/▼).

4 catégories

SectionCouleurCritèreComportement
🚨 Urgent#ef4444client.label === 'urgent'Visible dans les villes/zones + ici. Boutons assignation par tournée.
⚠️ Important#f59e0bclient.label === 'important'Idem urgent.
🏋️ Hors format (+40kg)#a855f7Au moins 1 colis individuel ≥ 40kgExclus des villes/zones. Modal de sélection + impression BAL individuel. Alerte si assigné à une tournée.
🏝️ Dépendances (îles)#06b6d4Client dans une île dépendanteExclus des villes/zones. Groupé par île. Modal de sélection + impression BAL individuel.

Hors format — logique métier

Règle : Si un client a AU MOINS 1 colis pesant ≥ 40kg, tout le client (tous ses colis) est classé hors format. Un client avec 1 colis de 50kg + 3 colis de 2kg = hors format. Un client avec 10 colis de 10kg = normal (100kg total mais aucun colis individuel ≥ 40kg).

Dépendances — îles concernées

GroupeVillesCodes postaux
La DésiradeLa Désirade97127
Marie-GalanteGrand-Bourg, Capesterre-de-Marie-Galante, Saint-Louis97112, 97140, 97134
Les SaintesLes Saintes, Terre-de-Haut, Terre-de-Bas97136, 97137

Bon de Livraison (BAL) — format

1 page A4 par client. Utilisé pour hors format ET dépendances. Fonction generateHorsFormatPDF().

┌─────────────────────────────────────────────────┐
│  VOYEY.COM                │  DESTINATAIRE        │
│  372 impasse palétuviers  │  Nom du client       │
│  97122 Baie-Mahault       │  Adresse complète    │
│  [email protected]          │  Tél + Email         │
├─────────────────────────────────────────────────┤
│  🏋️ BON DE LIVRAISON    N° BL-HF-YYYYMMDD-XX   │
├─────────────────────────────────────────────────┤
│  Zone │ Étagères │ Poids total │ Nb colis       │
├─────────────────────────────────────────────────┤
│  # │ Sticker │ Type │ Poids │ Étagère │ Obs     │
│  1 │ 129431  │ B3   │ 45.2kg│ ETG923  │ ⚠️ HF  │
│  2 │ 127102  │ B3   │ 1.7kg │ ETG708  │ —       │
├─────────────────────────────────────────────────┤
│  Signature livreur │ Signature client │ Date    │
└─────────────────────────────────────────────────┘

Normalisation status v2

Mapping status : vr_tours_v2 a une contrainte valid_status qui accepte : draft, scheduled, in_progress, completed, cancelled. Le code JS normalise scheduledplanned partout dans loadToursMeta() et renderToursGrid() pour cohérence avec vr_tours_meta.

Performances (optimisation 31/03/2026)

25. Cohérence écriture 3 tables tournées

Audit et correction du 01/04/2026. Le système de tournées utilise 3 tables qui doivent rester synchronisées.

Les 3 tables

TableRôleClé
vr_toursLegacy — stocke clients_data, status, compteurs(tour_number, tour_date)
vr_tours_v2V2 — UUID, clients_data JSONB, status avec contrainteid (UUID), unique (tour_number, planned_date)
vr_tours_metaMétadonnées — agent, véhicule, schedule, tracking, report(tour_num, date), tour_key
Cause racine des orphelins T228/T229 : savePlanningTourClients() écrivait dans vr_tours (legacy) mais PAS dans vr_tours_v2. Quand assignClientsToTourByKey() cherchait ensuite dans v2, il ne trouvait rien et appelait create_tour_v2 sans p_tour_number → auto-incrément global (T228, T229...).

Matrice d'écriture (après correction)

Fonctionvr_toursvr_tours_v2vr_tours_meta
savePlanningTourClients()✅ INSERT/UPDATE✅ CREATE/UPDATE
updateTourStatus()✅ UPDATE✅ UPDATE✅ UPDATE
saveTourMeta()✅ UPSERT
saveTourMetaSimple()🗑️ DELETE old✅ UPSERT
saveTourMetaWithKey()✅ UPSERT
startTourDelivery()✅ UPDATE status✅ via saveTourMeta
deleteTourByNumber()✅ DELETE✅ DELETE✅ DELETE
deleteTourByKey()✅ DELETE✅ DELETE✅ DELETE
clearAllTodayTours()✅ DELETE✅ DELETE✅ DELETE

Mapping des status entre tables

Conceptvr_tours (français)vr_tours_v2 (contrainte)vr_tours_meta
Brouillonbrouillondraftdraft
Planifiéeplanifieescheduledplanned
En coursen_coursin_progressin_progress
Terminéetermineecompletedcompleted
Annuléeannuleecancelledcancelled
Le JS normalise scheduledplanned dans loadToursMeta() pour l'affichage. La contrainte v2 valid_status n'accepte PAS planned — utiliser scheduled pour les écritures v2.

Corrections appliquées (01/04/2026)

FonctionAvantAprès
savePlanningTourClients()Écrit uniquement vr_toursÉcrit vr_tours + vr_tours_v2 (avec p_tour_number) + remplit tourIdCache
updateTourStatus()Écrit uniquement vr_toursÉcrit vr_tours + vr_tours_v2 (status mappé) + vr_tours_meta
savePlanningTourClients() clients_dataSeulement id, email, name, address, city, phone+ zone, lat, lng, postalCode, parcels (données complètes pour vr-delivery.js)

Règle pour les futurs développements

TOUTE modification de tournée doit écrire dans les 3 tables. Si une fonction écrit dans vr_tours sans écrire dans vr_tours_v2, les données seront désynchronisées et des orphelins auto-incrémentés seront créés. Vérifier la matrice ci-dessus avant tout nouveau développement.
VROOM ! — Documentation technique v3.7 — Mise à jour 01/04/2026 — Voyey