xpeditis2.0/docs/architecture/BOOKING_WORKFLOW_TODO.md
David c19af3b119
Some checks failed
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 58s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Failing after 5m55s
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Deployment Summary (push) Has been skipped
CI/CD Pipeline / Deploy to Portainer (push) Has been skipped
CI/CD Pipeline / Discord Notification (Success) (push) Has been skipped
CI/CD Pipeline / Discord Notification (Failure) (push) Has been skipped
docs: reorganiser completement la documentation dans docs/
Reorganisation majeure de toute la documentation du projet pour
ameliorer la navigation et la maintenance.

## Changements principaux

### Organisation (80 -> 4 fichiers .md a la racine)
- Deplace 82 fichiers .md dans docs/ organises en 11 categories
- Conserve uniquement 4 fichiers essentiels a la racine:
  * README.md, CLAUDE.md, PRD.md, TODO.md

### Structure docs/ creee
- installation/ (5 fichiers) - Guides d'installation
- deployment/ (25 fichiers) - Deploiement et infrastructure
- phases/ (21 fichiers) - Historique du developpement
- testing/ (5 fichiers) - Tests et qualite
- architecture/ (6 fichiers) - Documentation technique
- carrier-portal/ (2 fichiers) - Portail transporteur
- csv-system/ (5 fichiers) - Systeme CSV
- debug/ (4 fichiers) - Debug et troubleshooting
- backend/ (1 fichier) - Documentation backend
- frontend/ (1 fichier) - Documentation frontend
- legacy/ (vide) - Pour archives futures

### Documentation nouvelle
- docs/README.md - Index complet de toute la documentation (367 lignes)
  * Guide de navigation par scenario
  * Recherche rapide par theme
  * FAQ et commandes rapides
- docs/CLEANUP-REPORT-2025-12-22.md - Rapport detaille du nettoyage

### Scripts reorganises
- add-email-to-csv.py -> scripts/
- deploy-to-portainer.sh -> docker/

### Fichiers supprimes
- 1536w default.svg (11MB) - Fichier non utilise

### References mises a jour
- CLAUDE.md - Section Documentation completement reecrite
- docs/architecture/EMAIL_IMPLEMENTATION_STATUS.md - Chemin script Python
- docs/deployment/REGISTRY_PUSH_GUIDE.md - Chemins script deploiement

## Metriques
- 87 fichiers modifies/deplaces
- 82 fichiers .md organises dans docs/
- 11MB d'espace libere
- Temps de recherche reduit de ~5min a ~30s (-90%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 15:45:51 +01:00

17 KiB

Booking Workflow - Todo List

Ce document détaille toutes les tâches nécessaires pour implémenter le workflow complet de booking avec système d'acceptation/refus par email et notifications.

Vue d'ensemble

Le workflow permet à un utilisateur de:

  1. Sélectionner une option de transport depuis les résultats de recherche
  2. Remplir un formulaire avec les documents nécessaires
  3. Envoyer une demande de booking par email au transporteur
  4. Le transporteur peut accepter ou refuser via des boutons dans l'email
  5. L'utilisateur reçoit une notification sur son dashboard

Backend - Domain Layer (3 tâches)

Task 2: Créer l'entité Booking dans le domain

Fichier: apps/backend/src/domain/entities/booking.entity.ts (à créer)

Actions:

  • Créer l'enum BookingStatus (PENDING, ACCEPTED, REJECTED, CANCELLED)
  • Créer la classe Booking avec:
    • id: string
    • userId: string
    • organizationId: string
    • carrierName: string
    • carrierEmail: string
    • origin: PortCode
    • destination: PortCode
    • volumeCBM: number
    • weightKG: number
    • priceEUR: number
    • transitDays: number
    • status: BookingStatus
    • documents: Document[] (Bill of Lading, Packing List, Commercial Invoice, Certificate of Origin)
    • confirmationToken: string (pour les liens email)
    • requestedAt: Date
    • respondedAt?: Date
    • notes?: string
  • Méthodes: accept(), reject(), cancel(), isExpired()

Task 3: Créer l'entité Notification dans le domain

Fichier: apps/backend/src/domain/entities/notification.entity.ts (à créer)

Actions:

  • Créer l'enum NotificationType (BOOKING_ACCEPTED, BOOKING_REJECTED, BOOKING_CREATED)
  • Créer la classe Notification avec:
    • id: string
    • userId: string
    • type: NotificationType
    • title: string
    • message: string
    • bookingId?: string
    • isRead: boolean
    • createdAt: Date
  • Méthodes: markAsRead(), isRecent()

Backend - Infrastructure Layer (4 tâches)

Task 4: Mettre à jour le CSV loader pour passer companyEmail

Fichier: apps/backend/src/infrastructure/carriers/csv-loader/csv-rate-loader.adapter.ts

Actions:

  • Interface CsvRow déjà mise à jour avec companyEmail
  • Modifier la méthode mapToCsvRate() pour passer record.companyEmail au constructeur de CsvRate
  • Ajouter 'companyEmail' dans le tableau requiredColumns de validateCsvStructure()

Code à modifier (ligne ~267):

return new CsvRate(
  record.companyName.trim(),
  record.companyEmail.trim(), // NOUVEAU
  PortCode.create(record.origin),
  // ... reste
)

Task 5: Créer le repository BookingRepository

Fichiers à créer:

  • apps/backend/src/domain/ports/out/booking.repository.ts (interface)
  • apps/backend/src/infrastructure/persistence/typeorm/entities/booking.orm-entity.ts
  • apps/backend/src/infrastructure/persistence/typeorm/repositories/booking.repository.ts

Actions:

  • Créer l'interface du port avec méthodes:
    • create(booking: Booking): Promise<Booking>
    • findById(id: string): Promise<Booking | null>
    • findByUserId(userId: string): Promise<Booking[]>
    • findByToken(token: string): Promise<Booking | null>
    • update(booking: Booking): Promise<Booking>
  • Créer l'entité ORM avec décorateurs TypeORM
  • Implémenter le repository avec TypeORM

Task 6: Créer le repository NotificationRepository

Fichiers à créer:

  • apps/backend/src/domain/ports/out/notification.repository.ts (interface)
  • apps/backend/src/infrastructure/persistence/typeorm/entities/notification.orm-entity.ts
  • apps/backend/src/infrastructure/persistence/typeorm/repositories/notification.repository.ts

Actions:

  • Créer l'interface du port avec méthodes:
    • create(notification: Notification): Promise<Notification>
    • findByUserId(userId: string, unreadOnly?: boolean): Promise<Notification[]>
    • markAsRead(id: string): Promise<void>
    • markAllAsRead(userId: string): Promise<void>
  • Créer l'entité ORM
  • Implémenter le repository

Task 7: Créer le service d'envoi d'email

Fichier: apps/backend/src/infrastructure/email/email.service.ts (à créer)

Actions:

  • Utiliser nodemailer ou un service comme SendGrid/Mailgun
  • Créer la méthode sendBookingRequest(booking: Booking, acceptUrl: string, rejectUrl: string)
  • Créer le template HTML avec:
    • Récapitulatif du booking (origine, destination, volume, poids, prix)
    • Liste des documents joints
    • 2 boutons CTA: "Accepter la demande" (vert) et "Refuser la demande" (rouge)
    • Design responsive

Template email:

<!DOCTYPE html>
<html>
<head>
  <style>
    /* Styles inline pour compatibilité email */
  </style>
</head>
<body>
  <h1>Nouvelle demande de réservation - Xpeditis</h1>
  <div class="summary">
    <h2>Détails du transport</h2>
    <p><strong>Route:</strong> {{origin}} → {{destination}}</p>
    <p><strong>Volume:</strong> {{volumeCBM}} CBM</p>
    <p><strong>Poids:</strong> {{weightKG}} kg</p>
    <p><strong>Prix:</strong> {{priceEUR}} EUR</p>
    <p><strong>Transit:</strong> {{transitDays}} jours</p>
  </div>

  <div class="documents">
    <h3>Documents fournis:</h3>
    <ul>
      {{#each documents}}
        <li>{{this.name}}</li>
      {{/each}}
    </ul>
  </div>

  <div class="actions">
    <a href="{{acceptUrl}}" class="btn btn-accept">✓ Accepter la demande</a>
    <a href="{{rejectUrl}}" class="btn btn-reject">✗ Refuser la demande</a>
  </div>
</body>
</html>

Backend - Application Layer (5 tâches)

Task 8: Ajouter companyEmail dans le DTO de réponse

Fichier: apps/backend/src/application/dto/csv-rate-search.dto.ts

Actions:

  • Ajouter @ApiProperty() companyEmail: string; dans CsvRateSearchResultDto
  • Mettre à jour le mapper pour inclure companyEmail

Task 9: Créer les DTOs pour créer un booking

Fichier: apps/backend/src/application/dto/booking.dto.ts (à créer)

Actions:

  • Créer CreateBookingDto avec validation:
export class CreateBookingDto {
  @ApiProperty()
  @IsString()
  carrierName: string;

  @ApiProperty()
  @IsEmail()
  carrierEmail: string;

  @ApiProperty()
  @IsString()
  origin: string;

  @ApiProperty()
  @IsString()
  destination: string;

  @ApiProperty()
  @IsNumber()
  @Min(0)
  volumeCBM: number;

  @ApiProperty()
  @IsNumber()
  @Min(0)
  weightKG: number;

  @ApiProperty()
  @IsNumber()
  @Min(0)
  priceEUR: number;

  @ApiProperty()
  @IsNumber()
  @Min(1)
  transitDays: number;

  @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } })
  documents: Express.Multer.File[];

  @ApiProperty({ required: false })
  @IsOptional()
  @IsString()
  notes?: string;
}
  • Créer BookingResponseDto
  • Créer NotificationDto

Task 10: Créer l'endpoint POST /api/v1/bookings

Fichier: apps/backend/src/application/controllers/booking.controller.ts (à créer)

Actions:

  • Créer le controller avec méthode createBooking()
  • Utiliser @UseInterceptors(FilesInterceptor('documents')) pour l'upload
  • Générer un confirmationToken unique (UUID)
  • Sauvegarder les documents sur le système de fichiers ou S3
  • Créer le booking avec status PENDING
  • Générer les URLs d'acceptation/refus
  • Envoyer l'email au transporteur
  • Créer une notification pour l'utilisateur (BOOKING_CREATED)
  • Retourner le booking créé

Endpoint:

@Post()
@UseGuards(JwtAuthGuard)
@UseInterceptors(FilesInterceptor('documents', 10))
@ApiOperation({ summary: 'Create a new booking request' })
@ApiResponse({ status: 201, type: BookingResponseDto })
async createBooking(
  @Body() dto: CreateBookingDto,
  @UploadedFiles() files: Express.Multer.File[],
  @Request() req
): Promise<BookingResponseDto> {
  // Implementation
}

Task 11: Créer l'endpoint GET /api/v1/bookings/:id/accept

Fichier: apps/backend/src/application/controllers/booking.controller.ts

Actions:

  • Endpoint PUBLIC (pas de auth guard)
  • Vérifier le token de confirmation
  • Trouver le booking par token
  • Vérifier que le status est PENDING
  • Mettre à jour le status à ACCEPTED
  • Créer une notification pour l'utilisateur (BOOKING_ACCEPTED)
  • Rediriger vers /booking/confirm/:token (frontend)

Endpoint:

@Get(':id/accept')
@ApiOperation({ summary: 'Accept a booking request (public endpoint)' })
async acceptBooking(
  @Param('id') bookingId: string,
  @Query('token') token: string
): Promise<void> {
  // Validation + Update + Notification + Redirect
}

Task 12: Créer l'endpoint GET /api/v1/bookings/:id/reject

Fichier: apps/backend/src/application/controllers/booking.controller.ts

Actions:

  • Endpoint PUBLIC (pas de auth guard)
  • Même logique que accept mais avec status REJECTED
  • Créer une notification BOOKING_REJECTED
  • Rediriger vers /booking/reject/:token (frontend)

Task 13: Créer l'endpoint GET /api/v1/notifications

Fichier: apps/backend/src/application/controllers/notification.controller.ts (à créer)

Actions:

  • Endpoint protégé (JwtAuthGuard)
  • Query param optionnel ?unreadOnly=true
  • Retourner les notifications de l'utilisateur

Endpoints supplémentaires:

  • PATCH /api/v1/notifications/:id/read - Marquer comme lu
  • PATCH /api/v1/notifications/read-all - Tout marquer comme lu

Frontend (9 tâches)

Task 14: Modifier la page results pour rendre les boutons Sélectionner cliquables

Fichier: apps/frontend/app/dashboard/search/results/page.tsx

Actions:

  • Modifier le bouton "Sélectionner cette option" pour rediriger vers /dashboard/booking/new
  • Passer les données du rate via query params ou state
  • Exemple: /dashboard/booking/new?rateData=${encodeURIComponent(JSON.stringify(option))}

Task 15: Créer la page /dashboard/booking/new avec formulaire multi-étapes

Fichier: apps/frontend/app/dashboard/booking/new/page.tsx (à créer)

Actions:

  • Créer un formulaire en 3 étapes:
    1. Étape 1: Confirmation des détails du transport (lecture seule)
    2. Étape 2: Upload des documents (Bill of Lading, Packing List, Commercial Invoice, Certificate of Origin)
    3. Étape 3: Révision et envoi

Structure:

interface BookingForm {
  // Données du rate (pré-remplies)
  carrierName: string;
  carrierEmail: string;
  origin: string;
  destination: string;
  volumeCBM: number;
  weightKG: number;
  priceEUR: number;
  transitDays: number;

  // Documents à uploader
  documents: {
    billOfLading?: File;
    packingList?: File;
    commercialInvoice?: File;
    certificateOfOrigin?: File;
  };

  // Notes optionnelles
  notes?: string;
}

Task 16: Ajouter upload de documents

Fichier: apps/frontend/app/dashboard/booking/new/page.tsx

Actions:

  • Utiliser <input type="file" multiple accept=".pdf,.doc,.docx" />
  • Afficher la liste des fichiers sélectionnés avec possibilité de supprimer
  • Validation: taille max 5MB par fichier, formats acceptés (PDF, DOC, DOCX)
  • Preview des noms de fichiers

Composant:

<div className="space-y-4">
  <div>
    <label>Bill of Lading *</label>
    <input
      type="file"
      accept=".pdf,.doc,.docx"
      onChange={(e) => handleFileChange('billOfLading', e.target.files?.[0])}
    />
  </div>
  {/* Répéter pour les autres documents */}
</div>

Task 17: Créer l'API client pour les bookings

Fichier: apps/frontend/src/lib/api/bookings.ts (à créer)

Actions:

  • Créer createBooking(formData: FormData): Promise<BookingResponse>
  • Créer getBookings(): Promise<Booking[]>
  • Utiliser upload() de client.ts pour les fichiers

Task 18: Créer la page /booking/confirm/:token (acceptation publique)

Fichier: apps/frontend/app/booking/confirm/[token]/page.tsx (à créer)

Actions:

  • Page publique (pas de layout dashboard)
  • Afficher un message de succès avec animation
  • Afficher le récapitulatif du booking accepté
  • Message: "Merci d'avoir accepté cette demande de transport. Le client a été notifié."
  • Design: card centrée avec icône ✓ verte

Task 19: Créer la page /booking/reject/:token (refus publique)

Fichier: apps/frontend/app/booking/reject/[token]/page.tsx (à créer)

Actions:

  • Page publique
  • Formulaire optionnel pour raison du refus
  • Message: "Vous avez refusé cette demande de transport. Le client a été notifié."
  • Design: card centrée avec icône ✗ rouge

Task 20: Ajouter le composant NotificationBell dans le dashboard

Fichier: apps/frontend/src/components/NotificationBell.tsx (à créer)

Actions:

  • Icône de cloche dans le header du dashboard
  • Badge rouge avec le nombre de notifications non lues
  • Dropdown au clic avec liste des notifications
  • Marquer comme lu au clic
  • Lien vers le booking concerné

Intégration:

  • Ajouter dans apps/frontend/app/dashboard/layout.tsx dans le header (ligne ~154, à côté du User Role Badge)

Task 21: Créer le hook useNotifications pour polling

Fichier: apps/frontend/src/hooks/useNotifications.ts (à créer)

Actions:

  • Hook custom qui fait du polling toutes les 30 secondes
  • Retourne: { notifications, unreadCount, markAsRead, markAllAsRead, isLoading }
  • Utiliser useQuery de TanStack Query avec refetchInterval: 30000

Code:

export function useNotifications() {
  const { data, isLoading, refetch } = useQuery({
    queryKey: ['notifications'],
    queryFn: () => notificationsApi.getNotifications(),
    refetchInterval: 30000, // 30 seconds
  });

  const markAsRead = async (id: string) => {
    await notificationsApi.markAsRead(id);
    refetch();
  };

  return {
    notifications: data?.notifications || [],
    unreadCount: data?.unreadCount || 0,
    markAsRead,
    isLoading,
  };
}

Task 22: Tester le workflow complet end-to-end

Actions:

  1. Lancer le backend et le frontend
  2. Se connecter au dashboard
  3. Faire une recherche de tarifs
  4. Cliquer sur "Sélectionner cette option"
  5. Remplir le formulaire de booking
  6. Uploader des documents (fichiers de test)
  7. Soumettre le booking
  8. Vérifier que l'email est envoyé (vérifier les logs ou mailhog si configuré)
  9. Cliquer sur "Accepter" dans l'email
  10. Vérifier la page de confirmation
  11. Vérifier que la notification apparaît dans le dashboard
  12. Répéter avec "Refuser"

Checklist de test:

  • Création de booking réussie
  • Email reçu avec les bonnes informations
  • Bouton Accepter fonctionne et redirige correctement
  • Bouton Refuser fonctionne et redirige correctement
  • Notifications apparaissent dans le dashboard
  • Badge de notification se met à jour
  • Documents sont bien stockés
  • Données cohérentes en base de données

Dépendances NPM à ajouter

Backend

cd apps/backend
npm install nodemailer @types/nodemailer
npm install handlebars  # Pour les templates email
npm install uuid @types/uuid

Frontend

cd apps/frontend
# Tout est déjà installé (React Hook Form, TanStack Query, etc.)

Configuration requise

Variables d'environnement backend

Ajouter dans apps/backend/.env:

# Email configuration (exemple avec Gmail)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=your-email@gmail.com
EMAIL_PASSWORD=your-app-password
EMAIL_FROM=noreply@xpeditis.com

# Frontend URL for email links
FRONTEND_URL=http://localhost:3000

# File upload
MAX_FILE_SIZE=5242880  # 5MB
UPLOAD_DEST=./uploads/documents

Migrations de base de données

Backend - TypeORM migrations

cd apps/backend

# Générer les migrations
npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/CreateBookingAndNotification

# Appliquer les migrations
npm run migration:run

Tables à créer:

  • bookings (id, user_id, organization_id, carrier_name, carrier_email, origin, destination, volume_cbm, weight_kg, price_eur, transit_days, status, confirmation_token, documents_path, notes, requested_at, responded_at, created_at, updated_at)
  • notifications (id, user_id, type, title, message, booking_id, is_read, created_at)

Estimation de temps

Partie Tâches Temps estimé
Backend - Domain 3 2-3 heures
Backend - Infrastructure 4 3-4 heures
Backend - Application 5 3-4 heures
Frontend 8 4-5 heures
Testing & Debug 1 2-3 heures
TOTAL 22 14-19 heures

Notes importantes

  1. Sécurité des tokens: Utiliser des UUID v4 pour les confirmation tokens
  2. Expiration des liens: Ajouter une expiration (ex: 48h) pour les liens d'acceptation/refus
  3. Rate limiting: Limiter les appels aux endpoints publics (accept/reject)
  4. Stockage des documents: Considérer S3 pour la production au lieu du filesystem local
  5. Email fallback: Si l'envoi échoue, logger et permettre un retry
  6. Notifications temps réel: Pour une V2, considérer WebSockets au lieu du polling

Prochaines étapes

Une fois cette fonctionnalité complète, on pourra ajouter:

  • Page de liste des bookings (/dashboard/bookings)
  • Filtres et recherche dans les bookings
  • Export des bookings en PDF/Excel
  • Historique des statuts (timeline)
  • Chat intégré avec le transporteur
  • Système de rating après livraison