
# Soumissions de formulaire

Chaque soumission de formulaire produit un enregistrement durable. Par défaut, elle atterrit dans le registre intégré `form_submissions` — le journal canonique de la plateforme « ce formulaire a reçu une réponse » — et, lorsque `submitTo.table` est défini, dans votre propre table également. Les deux écritures sont transactionnelles, vous ne vous retrouvez donc jamais avec une entrée de registre promettant un enregistrement qui n'a jamais été écrit. Après la validation des écritures, `onSuccess` décide ce que le contributeur voit et `onError` couvre les échecs.

```yaml
forms:
  - id: 1
    name: contact
    submitTo:
      table: leads
      # storeSubmission defaults to true — table AND ledger
    fields:
      - { kind: table-field, column: email, required: true }
    onSuccess:
      type: toast
      variant: success
      message: Thanks! We'll be in touch.
```

## Le registre de soumissions

Le registre `form_submissions` réside dans un schéma interne géré par Sovrium (reflétant l'isolation de `auth.*`) — vous ne le créez ni ne le gérez jamais vous-même. Chaque soumission écrit une ligne de registre par défaut ; définissez `submitTo.storeSubmission: false` pour vous désinscrire (auquel cas `table` ou `automation` devient obligatoire).

| Colonne                | Type           | Description                                                                                     |
| ---------------------- | -------------- | ----------------------------------------------------------------------------------------------- |
| `id`                   | UUID           | Id de soumission stable ; exposé aux modèles `onSuccess` via `$submission.id`.                  |
| `form_name`            | text           | Le `forms[].name` au moment de la soumission.                                                   |
| `form_id`              | integer        | Le `forms[].id` au moment de la soumission (dénormalisé pour les jointures).                    |
| `submitted_at`         | timestamptz    | Horodatage serveur de la création de la ligne.                                                  |
| `submitter_user_id`    | UUID, nullable | L'utilisateur authentifié, lorsque le formulaire requiert l'authentification.                   |
| `submitter_ip`         | inet, nullable | IP du contributeur (stockée hachée par défaut) — utilisée par l'anti-spam et l'audit.           |
| `submitter_user_agent` | text, nullable | Chaîne user-agent du contributeur.                                                              |
| `data`                 | jsonb          | La charge utile complète de réponse validée (reflète ce qui a été écrit dans `submitTo.table`). |
| `linked_record_table`  | text, nullable | Le nom de la table liée, lorsque `submitTo.table` est défini.                                   |
| `linked_record_id`     | text, nullable | L'id de l'enregistrement inséré (typé text pour gérer les id integer / UUID / string).          |
| `status`               | enum           | État du cycle de vie (voir ci-dessous).                                                         |
| `status_reason`        | text, nullable | Brève raison lorsque `status` vaut `failed` ou `spam`.                                          |

## Statut du cycle de vie

| État         | Signification                                                                                | Transitions vers                                           |
| ------------ | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `received`   | Accepté ; écrit dans le registre et la table liée (le cas échéant).                          | `processing` (si automatisation), `done` (sinon).          |
| `processing` | L'automatisation liée s'exécute.                                                             | `done` en cas de succès, `failed` en cas d'échec terminal. |
| `done`       | Terminal — toutes les écritures et l'automatisation liée sont achevées.                      | —                                                          |
| `spam`       | Terminal — le pipeline anti-spam l'a classé comme spam ; conservé pour modération.           | —                                                          |
| `failed`     | Terminal — une étape en aval a échoué et n'a pas été réessayée ; `status_reason` est défini. | —                                                          |

## Le contrat d'écriture double

Lorsque `submitTo.table` est défini, la soumission écrit une ligne dans cette table **et** une ligne de registre au sein d'une seule transaction. Si l'écriture dans la table échoue — violation de contrainte, erreur de type ou clé étrangère manquante — la ligne de registre est annulée elle aussi, et le contributeur reçoit une erreur HTTP 4xx/5xx avec `fieldErrors:[{ name, message }]` nommant la colonne fautive. Aucune entrée de registre « fantôme » n'est jamais laissée derrière.

Lorsque `submitTo.automation` est défini, l'automatisation est invoquée **après** la validation des deux écritures. Un échec d'automatisation **n'annule pas** les écritures — la soumission est enregistrée, `status` passe à `failed`, et la propre machinerie de réessai/échec de l'automatisation s'applique.

```yaml
forms:
  - id: 2
    name: stream-event
    submitTo:
      automation: post-event-to-stream
      storeSubmission: false # opt out — ledger is skipped; automation handles persistence
    fields:
      - { kind: standalone, name: event_payload, inputType: long-text }
```

:::callout
**Ne contournez le registre que lorsque vous le voulez vraiment.** `storeSubmission: false` est le seul moyen de sauter le registre et est destiné aux formulaires à très haut volume qui acheminent vers un flux ou une file d'attente. Chaque fois qu'il est défini, `submitTo` doit toujours spécifier une `table` ou une `automation`, sinon la validation échoue — un formulaire qui ne persiste nulle part est un bug de configuration.
:::

## En cas de succès

L'objet `onSuccess` décide ce que le contributeur voit après une soumission réussie. C'est une union discriminée sur `type`.

| `type`        | Comportement                                                                                                                                                     |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `successPage` | Affiche une page de succès personnalisée (`title`, `message`, `buttonLabel` + `buttonHref` optionnels, `actions[]` optionnels, `showSummary`).                   |
| `redirect`    | Navigue vers `url` après un `delaySeconds` optionnel (par défaut 2 ; `0` est immédiat). Prend en charge les variables de modèle `$submission.id` / `$record.id`. |
| `reset`       | Vide le formulaire pour une nouvelle soumission, avec un `message` optionnel et `preserveFields[]` à conserver.                                                  |
| `toast`       | Affiche un toast transitoire (`message`, `variant` optionnel : `success` / `info`).                                                                              |
| `message`     | Remplace le formulaire par un `message` en ligne à sa place.                                                                                                     |

```yaml
onSuccess:
  type: redirect
  url: /thank-you?ref=$submission.id
  delaySeconds: 0
```

Une `successPage` peut afficher des boutons `actions[]` — chacun est soit `action: reset` (soumettre à nouveau) soit `action: navigate` (lien vers `url`) — et `showSummary: true` liste les valeurs soumises, en respectant les champs masqués et les permissions de lecture par champ.

## En cas d'erreur

L'objet `onError` contrôle le message affiché lorsqu'une soumission échoue côté serveur.

| Propriété | Description                                                                              |
| --------- | ---------------------------------------------------------------------------------------- |
| `type`    | `toast`, `message` (en ligne), ou `errorPage` (page complète).                           |
| `message` | Message du corps. Prend en charge les clés `$t:`.                                        |
| `title`   | Titre optionnel (utilisé par `errorPage`).                                               |
| `variant` | Variante de toast (`error` / `warning`) — significatif uniquement lorsque `type: toast`. |

```yaml
onError:
  type: message
  message: Something went wrong saving your request. Please try again.
```

:::callout
**Les erreurs de validation au niveau du champ sont distinctes.** Les échecs de validation côté serveur (par ex. un e-mail invalide ou une violation de clé étrangère) renvoient HTTP 400 avec une enveloppe `fieldErrors:[{ name, message }]` et sont affichés en ligne sur le champ fautif — indépendamment de `onError`, qui couvre les échecs de soumission entière.
:::

## Pages associées

- [Présentation des formulaires](/fr/docs/forms-overview) — `submitTo`, `storeSubmission`, et le schéma `onSuccess` / `onError`.
- [Champs de formulaire](/fr/docs/form-fields) — `permissions` par champ qui masquent les exports de registre.
- [Téléversements de fichiers](/fr/docs/form-file-uploads) — métadonnées de fichier stockées dans la charge utile `data` du registre.
- [Présentation des tables](/fr/docs/tables-overview) — la table liée que l'écriture double cible.
