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

# Modèle Candidature

> Schéma candidature, pipeline de statuts et événements

# Candidature

Une **candidature** (`Candidature`) est la soumission d'un candidat pour une offre donnée, avec CV et lettre de motivation.

## Schéma

```mermaid theme={null}
classDiagram
    class Candidature {
        +Long id
        +Long offreId
        +Long candidatId
        +Long cvId
        +Text lettreMotivation
        +String status
        +String recruteurNote
        +Instant appliedAt
        +Instant lastStatusChange
    }

    class CandidatureEvent {
        +Long id
        +Long candidatureId
        +String type "STATUS_CHANGE|NOTE|MESSAGE"
        +String fromStatus
        +String toStatus
        +Text content
        +Long authorId
        +Instant createdAt
    }

    class Offre {
        +Long id
        +String titre
    }

    class Candidat {
        +Long id
        +String firstName
    }

    Offre "1" --> "0..*" Candidature
    Candidat "1" --> "0..*" Candidature
    Candidature "1" --> "0..*" CandidatureEvent
```

## Exemple JSON

```json theme={null}
{
  "id": 9001,
  "offre": { "id": 123, "titre": "Développeur Backend Java (H/F)" },
  "candidat": { "id": 42, "firstName": "Jean", "lastName": "Ngassa" },
  "cv": { "id": 17, "fileName": "cv-jean-ngassa.pdf", "downloadUrl": "/v1/api/files/cv/17" },
  "lettreMotivation": "Madame, Monsieur, je vous présente ma candidature...",
  "status": "IN_PROGRESS",
  "appliedAt": "2026-04-16T14:20:00Z",
  "lastStatusChange": "2026-04-17T09:00:00Z",
  "timeline": [
    { "type": "STATUS_CHANGE", "fromStatus": null, "toStatus": "NEW", "at": "2026-04-16T14:20:00Z" },
    { "type": "STATUS_CHANGE", "fromStatus": "NEW", "toStatus": "TO_MEET", "at": "2026-04-16T16:45:00Z" },
    { "type": "NOTE", "content": "Bon profil, à contacter", "authorId": 42, "at": "2026-04-17T09:00:00Z" },
    { "type": "STATUS_CHANGE", "fromStatus": "TO_MEET", "toStatus": "IN_PROGRESS", "at": "2026-04-17T09:00:00Z" }
  ]
}
```

## Pipeline de statuts

```mermaid theme={null}
stateDiagram-v2
    [*] --> NEW: POST /offres/:id/apply
    NEW --> TO_MEET: marquer à rencontrer
    NEW --> REJECTED: refuser
    TO_MEET --> IN_PROGRESS: entretien planifié
    IN_PROGRESS --> INTERVIEWED: entretien effectué
    INTERVIEWED --> HIRED: retenu
    INTERVIEWED --> REJECTED: non retenu
    HIRED --> [*]
    REJECTED --> [*]

    note right of NEW
        Candidat voit: "En cours"
    end note
    note right of HIRED
        Candidat notifié
    end note
```

| Statut        | Description         | Côté candidat                    |
| ------------- | ------------------- | -------------------------------- |
| `NEW`         | Reçue, non traitée  | "En cours d'examen"              |
| `TO_MEET`     | Recruteur intéressé | "En cours d'examen"              |
| `IN_PROGRESS` | Traitement actif    | "En cours d'examen"              |
| `INTERVIEWED` | Entretien passé     | "Entretien effectué"             |
| `HIRED`       | Retenu              | "Félicitations, retenu !"        |
| `REJECTED`    | Refusé              | "Non retenu" (+ motif optionnel) |
| `WITHDRAWN`   | Retiré par candidat | "Retirée"                        |

## Transitions autorisées

```mermaid theme={null}
flowchart LR
    NEW --> TM[TO_MEET]
    NEW --> REJ[REJECTED]
    NEW --> WD[WITHDRAWN]
    TM --> IP[IN_PROGRESS]
    TM --> REJ
    IP --> INT[INTERVIEWED]
    IP --> REJ
    INT --> HI[HIRED]
    INT --> REJ
    HI --> ROL[rollback 24h\n → INTERVIEWED]
    REJ --> ROL2[rollback 24h\n → IN_PROGRESS]
```

<Info>
  Un **rollback 24h** est possible après `HIRED` / `REJECTED` pour corriger une erreur de clic. Passé ce délai, la décision est verrouillée.
</Info>

## Événements émis

```mermaid theme={null}
sequenceDiagram
    participant R as Recruteur
    participant API as Backend
    participant N as Notification
    participant C as Candidat

    R->>API: PATCH /candidatures/9001\n {status: TO_MEET}
    API->>API: UPDATE + INSERT event
    API->>N: candidature.status-changed
    N->>C: Email FR + notif in-app
    API-->>R: 200
```

## Double-postulation interdite

```sql theme={null}
CREATE UNIQUE INDEX uq_candidature_active
  ON candidatures(offre_id, candidat_id)
  WHERE status NOT IN ('REJECTED', 'WITHDRAWN');
```

```mermaid theme={null}
flowchart TD
    AP[POST /offres/123/apply] --> CK{candidature\n existante active?}
    CK -->|oui| R409[409 Conflict]
    CK -->|non et ancienne REJECTED| OK1[Nouvelle candidature possible]
    CK -->|non| OK2[Candidature créée]
```

## Endpoints principaux

| Méthode | Path                                              | Rôle                |
| ------- | ------------------------------------------------- | ------------------- |
| `POST`  | `/v1/api/offres/{id}/apply`                       | Candidat postule    |
| `GET`   | `/v1/api/candidats/me/candidatures`               | Historique candidat |
| `GET`   | `/v1/api/recruteur/candidatures`                  | File recruteur      |
| `PATCH` | `/v1/api/recruteur/candidatures/{id}`             | Changer statut      |
| `POST`  | `/v1/api/candidats/me/candidatures/{id}/withdraw` | Retirer             |
| `GET`   | `/v1/api/admin/candidatures/{id}`                 | Admin               |

## Suppression / anonymisation

Une candidature ne peut pas être supprimée — elle peut être anonymisée en cas de droit à l'effacement du candidat. `candidatId` devient `NULL`, les notes recruteur sont conservées pour les stats.
