> ## 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.

# Cycle de vie d'une offre

> États d'une offre d'emploi, transitions et règles métier

# Cycle de vie d'une offre

Une offre traverse quatre états principaux : **DRAFT → PUBLISHED → CLOSED → ARCHIVED**.

## Diagramme d'états

```mermaid theme={null}
stateDiagram-v2
    [*] --> DRAFT: POST /offres
    DRAFT --> PUBLISHED: publish
    DRAFT --> DRAFT: update
    DRAFT --> ARCHIVED: archive
    PUBLISHED --> CLOSED: close manuel / expiration auto
    PUBLISHED --> DRAFT: unpublish
    PUBLISHED --> PUBLISHED: update (limité)
    CLOSED --> PUBLISHED: republish (si dateExpiration prolongée)
    CLOSED --> ARCHIVED: archive
    ARCHIVED --> [*]

    note right of DRAFT
        Non visible publiquement
    end note
    note right of PUBLISHED
        Indexée + visible
        Accepte candidatures
    end note
    note right of CLOSED
        Visible mais pas de candidature
    end note
    note right of ARCHIVED
        Non listée, non indexée
    end note
```

## États détaillés

| État        | Visible public          | Candidatures | Indexée FTS | Dashboard recruteur      |
| ----------- | ----------------------- | ------------ | ----------- | ------------------------ |
| `DRAFT`     | Non                     | Non          | Non         | Oui                      |
| `PUBLISHED` | Oui                     | Oui          | Oui         | Oui                      |
| `CLOSED`    | Oui (profil entreprise) | Non          | Non         | Oui                      |
| `ARCHIVED`  | Non                     | Non          | Non         | Oui (filtre "archivées") |

## Transitions autorisées

```mermaid theme={null}
flowchart LR
    D[DRAFT] -->|publish + validations| P[PUBLISHED]
    D -->|archive| A[ARCHIVED]
    P -->|close / date exp.| C[CLOSED]
    P -->|unpublish| D
    C -->|republish| P
    C -->|archive| A
```

## Validations à la publication

```mermaid theme={null}
flowchart TD
    PUB[POST /offres/:id/publish] --> V1{titre non vide}
    V1 -->|non| E1[422 titre obligatoire]
    V1 -->|oui| V2{description >= 50 car}
    V2 -->|non| E2[422 description trop courte]
    V2 -->|oui| V3{typeContrat valide}
    V3 -->|non| E3[422 type contrat invalide]
    V3 -->|oui| V4{recruteur ACTIVE}
    V4 -->|non| E4[403 recruteur non validé]
    V4 -->|oui| V5{quota offres\n entreprise ok}
    V5 -->|non| E5[402 quota dépassé]
    V5 -->|oui| V6{description sans\n téléphone/email}
    V6 -->|non| E6[422 contact dans description]
    V6 -->|oui| V7{dateExpiration\n > aujourd'hui}
    V7 -->|non| E7[422 date expirée]
    V7 -->|oui| OK[200 + index FTS\n + publier_at=NOW]
```

## Modifications en PUBLISHED

Certains champs sont **verrouillés** une fois publié pour éviter le bait-and-switch :

| Champ                 | Modifiable en PUBLISHED           |
| --------------------- | --------------------------------- |
| `titre`               | Non                               |
| `typeContrat`         | Non                               |
| `ville`               | Non                               |
| `salaireMin/Max`      | Non (sauf prolongation par admin) |
| `description`         | Oui                               |
| `competencesRequises` | Oui                               |
| `dateExpiration`      | Oui (future uniquement)           |

<Info>
  Pour changer un champ verrouillé, il faut `unpublish` → modifier en DRAFT → `publish` à nouveau. Les candidatures existantes restent attachées.
</Info>

## Expiration automatique

```mermaid theme={null}
sequenceDiagram
    participant CR as Cron (hourly)
    participant DB as PostgreSQL
    participant N as NotificationService

    CR->>DB: UPDATE offres\n SET status='CLOSED'\n WHERE status='PUBLISHED'\n AND date_expiration < NOW()
    DB-->>CR: N rows
    CR->>N: Pour chaque: notifyRecruteur("offre expirée")
```

## Quota offres actives par plan

| Plan       | Max PUBLISHED simultanément |
| ---------- | --------------------------- |
| Gratuit    | 2                           |
| Essentiel  | 10                          |
| Pro        | 50                          |
| Entreprise | ∞                           |

```mermaid theme={null}
flowchart LR
    R[Recruteur publie] --> COUNT[SELECT count WHERE entrepriseId AND status=PUBLISHED]
    COUNT --> CHECK{< quota plan?}
    CHECK -->|oui| OK[Publier]
    CHECK -->|non| UPGR[402 Payment Required\n + lien upgrade plan]
```

## Impact sur les candidatures

```mermaid theme={null}
flowchart TD
    OFF[Offre PUBLISHED\n avec candidatures]
    OFF -->|unpublish| DR[DRAFT]
    DR -->|candidatures\n figées| STATE[Statuts inchangés\n mais nouvelles bloquées]

    OFF -->|close| CL[CLOSED]
    CL -->|candidatures\n traitables| PROC[Recruteur peut encore\n marquer HIRED/REJECTED]

    OFF -->|delete| DEL[DELETED]
    DEL --> ANON[Candidatures anonymisées\n offreId conservé pour stats]
```

## Suppression

Une offre `PUBLISHED` ne peut pas être supprimée — seulement archivée. Une offre `DRAFT` peut être supprimée. Une offre `ARCHIVED` peut être supprimée **après 30 jours** pour laisser le temps de contester.

```mermaid theme={null}
stateDiagram-v2
    DRAFT --> DELETED: delete immédiat
    ARCHIVED --> DELETED: delete après 30j
    PUBLISHED --> DELETED: interdit\n (403)
    CLOSED --> DELETED: interdit\n (403)
```

## Événements émis

| État source | État cible      | Événement         | Notif email                |
| ----------- | --------------- | ----------------- | -------------------------- |
| \*          | PUBLISHED       | `offre.published` | Abonnés alertes matchantes |
| PUBLISHED   | CLOSED (auto)   | `offre.expired`   | Recruteur                  |
| PUBLISHED   | CLOSED (manuel) | `offre.closed`    | –                          |
| \*          | ARCHIVED        | `offre.archived`  | –                          |

## Traces audit

Chaque transition d'état est loggée dans `offres_status_history` :

```sql theme={null}
CREATE TABLE offres_status_history (
  id BIGSERIAL PRIMARY KEY,
  offre_id BIGINT REFERENCES offres(id),
  from_status VARCHAR(20),
  to_status VARCHAR(20),
  changed_by BIGINT REFERENCES users(id),
  changed_at TIMESTAMPTZ DEFAULT NOW(),
  reason TEXT
);
```
