# 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):
```typescript
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**:
```html
<!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:
```typescript
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**:
```typescript
@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**:
```typescript
@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**:
```typescript
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**:
```typescript
<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**:
```typescript
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
```bash
cd apps/backend
npm install nodemailer @types/nodemailer
npm install handlebars  # Pour les templates email
npm install uuid @types/uuid
```

### Frontend
```bash
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`:
```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
```bash
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
