> ## Documentation Index
> Fetch the complete documentation index at: https://docs.colismove.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-colis & pricing

> Sc 12, 13, 14, 15 — réservation multi-colis, agence B2B, anti-bypass chat, prix par catégorie.

Quatre scénarios couvrent les fonctionnalités de scaling : partage d'une annonce entre plusieurs sender (Sc 12), comptes B2B (Sc 13), modération chat (Sc 14), et pricing différencié par catégorie (Sc 15).

## Sc 12 — Réservation multi-colis (8 kg partagé)

Un expéditeur réserve **8 kg** sur une annonce en créant **3 sous-réservations** (un parent + deux enfants). Chaque enfant correspond à un colis distinct, mais un seul `PaymentIntent` paie l'ensemble.

```mermaid theme={null}
flowchart TD
    A[Sender booke 8kg sur annonce 10kg] --> B[Création parent booking]
    B --> C[Création child #1<br/>3kg électronique]
    B --> D[Création child #2<br/>5kg documents]
    B --> E[Single PaymentIntent<br/>parent total = 8kg]
    E --> F[Stripe escrow]

    F --> G[Carrier accepte parent]
    G --> H[Cascade auto<br/>children → ACCEPTEE]
    H --> I[Sender dépose chaque enfant<br/>QR par enfant]
    I --> J[Cascade EN_COURS_DE_LIVRAISON]
    J --> K[Recipients scannent QR<br/>par enfant]
    K --> L[Cascade LIVREE]
    L --> M[Stripe transfer cumulé]
```

### Cycle parent ↔ enfants

```mermaid theme={null}
stateDiagram-v2
    state "Parent + Enfants" as multi {
        Parent --> Enfant1 : créateur cascade
        Parent --> Enfant2 : créateur cascade
    }
    multi --> ACCEPTEE_cascade : Carrier accepte parent
    ACCEPTEE_cascade --> EN_COURS_cascade : QR dépôts (par enfant)
    EN_COURS_cascade --> LIVREE_cascade : QR livraisons (par enfant)
    note right of multi
        - Single PaymentIntent sur parent
        - Refund cascade sur enfants
        - Transitions cascade auto
    end note
```

### Endpoint enfants

```http theme={null}
GET /v1/api/bookings/{parentId}/children
```

Retourne la liste des sous-réservations enfants d'un booking parent.

### Particularités

* Un seul `PaymentIntent` Stripe sur le parent
* Refund cascade sur enfants en cas d'annulation
* Transitions parent → enfants automatiques (ACCEPTEE, EN\_COURS\_DE\_LIVRAISON, LIVREE, ANNULEE)
* Si un enfant tombe en LITIGE → ne bloque pas les autres enfants

**Référence** : commit `2023944`

## Sc 13 — Agence B2B publie annonce groupée

Module dédié `colismove-agency` (74 fichiers, 11 use cases hex). Une agence professionnelle gère plusieurs branches et employés, et publie des annonces groupées (par exemple un convoi régulier Yaoundé → Paris).

```mermaid theme={null}
flowchart LR
    A[Agence Mère] --> B[Branche 1<br/>Yaoundé]
    A --> C[Branche 2<br/>Douala]
    A --> D[Branche 3<br/>Paris]
    B --> E[Employé 1]
    B --> F[Employé 2]
    C --> G[Employé 3]
    D --> H[Employé 4]
    A --> I[Annonces groupées<br/>publiées par l'agence]
    I --> J[Bookings affectés<br/>aux employés]
```

### Structure agence

| Entité              | Description                                                   |
| ------------------- | ------------------------------------------------------------- |
| **Agence**          | Compte B2B mère, KYC docs, Stripe Connect platform            |
| **Branche**         | Filiale par ville/région                                      |
| **Employé**         | Carrier individuel rattaché à une branche                     |
| **Annonce groupée** | Publiée par l'agence, affectée à un employé après acceptation |

### Tests E2E

Module `colismove-agency` couvert par tests d'intégration en CI continue.

## Sc 14 — Anti-contournement chat (regex filtre)

Service Firebase Firestore avec filtre regex appliqué côté backend (pas Firestore rules) pour détecter et bloquer les patterns d'évasion de plateforme (numéros de téléphone, emails, liens externes).

```mermaid theme={null}
sequenceDiagram
    actor S as Sender
    participant API as Backend
    participant FF as Firebase Firestore
    actor C as Carrier

    S->>API: POST /messages "Mon WhatsApp +33612345678"
    API->>API: Apply anti-bypass regex
    API->>API: Match phone pattern → blocked
    API-->>S: 422 MESSAGE_BLOQUE<br/>{forbiddenPattern: 'phone'}
    Note over API,S: Pas de transmission Firestore

    S->>API: POST /messages "Bonjour, à quelle heure ?"
    API->>API: Anti-bypass OK
    API->>FF: Write message
    FF-->>C: Real-time push
```

### Patterns bloqués

* Numéros de téléphone (avec ou sans indicatif)
* Adresses email
* Liens externes (`http://`, `www.`)
* Identifiants WhatsApp / Telegram

## Sc 15 — Prix par catégorie

**Epic 4 complète** (stories 4.1 → 4.11). Permet à un transporteur de définir un **prix par kg différent par catégorie acceptée** (ex : électronique 8€/kg, documents 2€/kg).

```mermaid theme={null}
flowchart TD
    A[Carrier crée annonce] --> B[Catégories acceptées<br/>électronique, documents, cadeaux]
    B --> C[Prix per-catégorie<br/>électronique=8€/kg, doc=2€/kg]
    C --> D[Annonce.pricePerKg<br/>fallback global]

    E[Sender booke 5kg électronique] --> F[Calcul prix]
    F --> G[COALESCE category.pricePerKg<br/>fallback annonce.pricePerKg]
    G --> H[40€ * 5kg = 40€ HT]
    H --> I[+ 15% commission ColisMove]
    I --> J[46€ TTC sender paie]
```

### Modèle domain (Story 4.2)

```java theme={null}
// AnnonceCategorie record
record AnnonceCategorie(
    Long categorieId,
    String nom,
    BigDecimal pricePerKg  // nullable, fallback annonce
) {
    BigDecimal effectivePricePerKg(BigDecimal annoncePricePerKg) {
        return Optional.ofNullable(pricePerKg).orElse(annoncePricePerKg);
    }
}

// Annonce.prixPourCategorie
BigDecimal prixPourCategorie(Long categorieId) {
    return categories.stream()
        .filter(c -> c.categorieId().equals(categorieId))
        .findFirst()
        .orElseThrow(() -> new CategorieIntrouvableException())
        .effectivePricePerKg(this.pricePerKg);
}
```

### Backward compat 100%

* Annonces existantes avec `pricePerKg` global → continuent de fonctionner
* `category.pricePerKg = NULL` → fallback `annonce.pricePerKg` via SQL `COALESCE`
* Plafond 999€/kg validé côté domain

### SQL recherche (Story 4.10)

```sql theme={null}
-- priceMaxLessThanForCategory(maxPrice, code)
INNER JOIN annonce_categorie ac ON ac.annonce_id = a.id
WHERE ac.code = :code
  AND COALESCE(ac.price_per_kg, a.price_per_kg) <= :maxPrice

-- orderByEffectivePriceForCategory(code, descending)
ORDER BY COALESCE(ac.price_per_kg, a.price_per_kg) ASC|DESC
```

### Stories Epic 4

| #    | Story                                                    | Statut  |
| ---- | -------------------------------------------------------- | ------- |
| 4.1  | V21 migration `price_per_kg` nullable                    | Done    |
| 4.2  | Domain `effectivePricePerKg` + record `CategoriePricing` | Done    |
| 4.3  | REST DTOs dual format                                    | Done    |
| 4.4  | `CalculPrixReservationService` BigDecimal HALF\_UP       | Done    |
| 4.5  | Persistence prix per-ligne                               | Done    |
| 4.6  | Filtre prix-effectif recherche                           | Done    |
| 4.7  | `AnnonceCategorieResult` enrichi                         | Done    |
| 4.8  | Stripe charge multi-prix domain                          | Done    |
| 4.9  | `categorieFilter` recherche                              | Done    |
| 4.10 | Specs SQL avancées                                       | Done    |
| 4.11 | JMH bench P95 \< 200ms / 50k dataset                     | Pending |

## Récap modules impactés

| Scénario | Modules backend                                                                |
| -------- | ------------------------------------------------------------------------------ |
| Sc 12    | `colismove-reservation` (cascade), `colismove-finances` (single PaymentIntent) |
| Sc 13    | `colismove-agency` (74 fichiers dédiés)                                        |
| Sc 14    | Service Firebase Firestore (côté backend, pas rules)                           |
| Sc 15    | `colismove-annonces`, `colismove-reservation`, `colismove-finances`            |
