account/docs/api.md
GnomeZworc 8a11a1d5ea
add pending path
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-06-14 22:29:35 +02:00

515 lines
13 KiB
Markdown

# API Reference
Base URL : `http://localhost:8080`
Toutes les réponses sont en `application/json`. Les erreurs retournent `{"error": "message"}`.
---
## Authentification
Toutes les routes (sauf `GET /health`) requièrent le header :
```
X-Owner-ID: <integer>
```
En dev, c'est l'ID numérique de l'owner. Les comptes, transactions et snapshots sont strictement filtrés par owner — un owner ne peut ni voir ni modifier les ressources d'un autre.
**Réponse `401`** — header absent ou invalide
```json
{ "error": "missing X-Owner-ID header" }
```
> À remplacer par JWT en production.
---
## Health
### `GET /health`
Vérifie la disponibilité du serveur et de la base de données.
**Réponse `200`**
```json
{ "status": "ok" }
```
**Réponse `503`** — base indisponible
---
## Instruments
Un instrument représente tout actif coté : devise (EUR), action, ETF, crypto.
Le prix EUR vaut toujours 1 et est inséré automatiquement au démarrage.
### `GET /instruments`
Liste tous les instruments.
**Réponse `200`**
```json
[
{ "id": 1, "type": "devise", "code": "EUR", "name": "Euro", "devise_cotation": "EUR" },
{ "id": 2, "type": "etf", "code": "LU1681043599", "name": "Amundi MSCI World", "devise_cotation": "EUR" }
]
```
---
### `GET /instruments/{id}`
**Réponse `200`**
```json
{ "id": 2, "type": "etf", "code": "LU1681043599", "name": "Amundi MSCI World", "devise_cotation": "EUR" }
```
**Réponse `404`** — instrument introuvable
---
### `POST /instruments`
**Corps**
| Champ | Type | Requis | Description |
|---|---|---|---|
| `type` | string | ✅ | `devise`, `action`, `etf`, `crypto` |
| `code` | string | ✅ | ISIN (actions/ETF), ticker (crypto), code devise |
| `name` | string | ✅ | Nom lisible |
| `devise_cotation` | string | | Devise de cotation (défaut : `EUR`) |
**Réponse `201`**
```json
{ "id": 3, "type": "crypto", "code": "BTC", "name": "Bitcoin", "devise_cotation": "EUR" }
```
> **Pipeline de prix** : le ticker Yahoo Finance (actions/ETF) est résolu automatiquement via OpenFIGI au prochain cycle. L'ID CoinGecko (crypto) est résolu via l'API search CoinGecko. Les résolutions sont mises en cache dans `instrument_ticker_cache`.
---
### `PUT /instruments/{id}`
Même corps que `POST`. Retourne l'instrument mis à jour.
---
### `DELETE /instruments/{id}`
**Réponse `204`** — supprimé
---
## Comptes
Un compte est un conteneur de positions. Son solde est entièrement dérivé des transactions (aucun solde stocké).
Les enveloppes sont des sous-comptes monétaires liés à un compte maître (voir section Enveloppes).
`GET /accounts/{id}` retourne toujours le compte avec sa liste d'enveloppes.
### `GET /accounts`
Liste les comptes maîtres (sans les enveloppes).
**Réponse `200`**
```json
[
{
"id": 1,
"nom": "Livret A",
"type": "livret",
"devise_reference": "EUR",
"plafond": 22950
}
]
```
---
### `GET /accounts/{id}`
Retourne le compte et ses enveloppes.
**Réponse `200`**
```json
{
"id": 1,
"nom": "Livret A",
"type": "livret",
"devise_reference": "EUR",
"plafond": 22950,
"envelopes": [
{
"id": 3,
"nom": "Vacances",
"type": "livret",
"devise_reference": "EUR",
"master_account_id": 1,
"objectif": "Road trip USA"
}
]
}
```
**Réponse `404`** — compte introuvable
---
### `POST /accounts`
Crée un compte maître.
**Corps**
| Champ | Type | Requis | Description |
|---|---|---|---|
| `nom` | string | ✅ | Nom du compte |
| `type` | string | ✅ | `courant`, `livret`, `pea`, `cto`, `crypto`, ... |
| `devise_reference` | string | | Devise de référence (défaut : `EUR`) |
| `plafond` | number | | Plafond réglementaire (indicatif) |
**Réponse `201`**
```json
{ "id": 1, "nom": "Livret A", "type": "livret", "devise_reference": "EUR", "plafond": 22950 }
```
---
### `PUT /accounts/{id}`
Met à jour un compte maître. Même corps que `POST`.
---
### `DELETE /accounts/{id}`
Supprime le compte **et toutes ses enveloppes** (CASCADE).
**Réponse `204`**
---
## Enveloppes
Une enveloppe est un sous-compte monétaire rattaché à un compte maître. Elle hérite automatiquement du `type` et de la `devise_reference` de son maître. Le solde est dérivé des transactions, comme pour tout compte.
Règles :
- Un compte maître ne peut pas lui-même être une enveloppe (pas de chaînage)
- Supprimée automatiquement avec son compte maître
### `GET /accounts/{id}/envelopes`
Liste les enveloppes d'un compte.
**Réponse `200`**
```json
[
{
"id": 3,
"nom": "Vacances",
"type": "livret",
"devise_reference": "EUR",
"master_account_id": 1,
"objectif": "Road trip USA"
}
]
```
---
### `POST /accounts/{id}/envelopes`
Crée une enveloppe sous le compte `{id}`.
**Corps**
| Champ | Type | Requis | Description |
|---|---|---|---|
| `nom` | string | ✅ | Nom de l'enveloppe |
| `objectif` | string | | Description libre de l'objectif |
**Réponse `201`**
```json
{
"id": 3,
"nom": "Vacances",
"type": "livret",
"devise_reference": "EUR",
"master_account_id": 1,
"objectif": "Road trip USA"
}
```
**Réponse `400`** — le compte est lui-même une enveloppe (chaînage interdit)
**Réponse `404`** — compte introuvable
---
### `PUT /envelopes/{id}`
Met à jour le nom et/ou l'objectif d'une enveloppe.
**Corps**
| Champ | Type | Requis | Description |
|---|---|---|---|
| `nom` | string | ✅ | Nouveau nom |
| `objectif` | string | | Nouvel objectif |
**Réponse `200`** — enveloppe mise à jour
**Réponse `404`** — enveloppe introuvable
---
### `DELETE /envelopes/{id}`
Supprime l'enveloppe.
**Réponse `204`**
---
## Transactions
Une transaction est un échange atomique entre deux participants (comptes ou enveloppes) via des instruments.
Chaque côté (source, dest) est soit entièrement renseigné soit entièrement null. Au moins un côté doit exister.
**Cas couverts :**
| Cas | Source | Dest |
|---|---|---|
| Flux entrant (salaire, dépôt) | null | compte + instrument + quantité |
| Flux sortant (dépense) | compte + instrument + quantité | null |
| Virement / allocation enveloppe | compte + instrument + quantité | compte + instrument + quantité |
| Achat de titres | compte, EUR, montant | compte, ETF, nb parts |
| Swap crypto | wallet, BTC, qté | wallet, ETH, qté |
Le champ `validated` indique si la transaction est réelle (`true`) ou prévisionnelle (`false`).
Les transactions non validées à date dépassée remontent dans `?pending=true` (liste "à traiter").
---
### `GET /accounts/{id}/transactions`
Vue centrée sur le compte : le `montant` est signé (positif = crédit, négatif = débit).
`instrument_id` est celui du côté du compte concerné.
`contrepartie_account_id` est le compte en face (null si flux externe).
**Paramètres de filtre**
| Paramètre | Type | Description |
|---|---|---|
| `validated` | boolean | `true` ou `false` |
| `pending` | boolean | `true` → non validées avec date ≤ aujourd'hui |
| `from` | string | Date de début `YYYY-MM-DD` |
| `to` | string | Date de fin `YYYY-MM-DD` |
**Réponse `200`** — triée par date DESC
```json
[
{
"id": 1,
"date": "2026-06-13",
"montant": 2800.00,
"instrument_id": 1,
"tiers": "Employeur",
"label": "Salaire juin",
"categorie": "salaire",
"validated": true
},
{
"id": 2,
"date": "2026-06-10",
"montant": -50.00,
"instrument_id": 1,
"label": "Courses",
"categorie": "alimentation",
"validated": true
}
]
```
---
### `GET /transactions/{id}`
Retourne la transaction brute (tous les champs source/dest).
**Réponse `200`**
```json
{
"id": 1,
"date": "2026-06-13",
"account_dest_id": 1,
"instrument_dest_id": 1,
"quantite_dest": 2800,
"tiers": "Employeur",
"label": "Salaire juin",
"categorie": "salaire",
"validated": true
}
```
**Réponse `404`** — introuvable
---
### `POST /transactions`
**Corps**
| Champ | Type | Requis | Description |
|---|---|---|---|
| `date` | string | ✅ | `YYYY-MM-DD` |
| `label` | string | ✅ | Libellé |
| `validated` | boolean | | Défaut `false` |
| `account_source_id` | integer | | Compte source |
| `instrument_source_id` | integer | | Instrument source |
| `quantite_source` | number | | Quantité source |
| `account_dest_id` | integer | | Compte dest |
| `instrument_dest_id` | integer | | Instrument dest |
| `quantite_dest` | number | | Quantité dest |
| `tiers` | string | | Tiers externe (employeur, commerçant…) |
| `categorie` | string | | Catégorie libre |
> Les trois champs de chaque côté (account, instrument, quantite) doivent être tous renseignés ou tous null.
**Réponse `201`** — transaction créée (format brut)
**Réponse `400`** — body invalide, label manquant, date manquante, ou violation de la contrainte source/dest
---
### `PUT /transactions/{id}`
Mise à jour complète. Même corps que `POST`.
**Réponse `200`** — transaction mise à jour (format brut)
**Réponse `404`** — introuvable
---
### `PATCH /transactions/{id}/validate`
Valide ou dé-valide une transaction.
**Corps**
```json
{ "validated": true }
```
**Réponse `200`** — transaction avec le nouveau statut
**Réponse `404`** — introuvable
---
### `DELETE /transactions/{id}`
**Réponse `204`**
---
## Snapshots
Les snapshots capturent la valorisation de chaque compte sur une fenêtre glissante :
- **Passé** : transactions validées uniquement
- **Futur** : toutes les transactions (validées ou non) — projection basée sur les transactions planifiées
Deux tables sont alimentées :
- `position_snapshot` — quantité + valeur par instrument et par compte
- `account_snapshot` — valeur totale agrégée par compte
**Fenêtre** : de la première transaction jusqu'à `today + SNAPSHOT_HORIZON_DAYS` (défaut : 30 jours). Configurable via la variable d'environnement `SNAPSHOT_HORIZON_DAYS`.
**Toutes les transactions sont comptées** quelle que soit leur validation.
Le snapshot représente toujours l'état envisagé complet du compte :
transactions confirmées (`validated = true`) **et** planifiées (`validated = false`).
La liste `?pending=true` (transactions non validées à date dépassée) reste distincte — c'est un outil de suivi, pas un filtre de calcul.
**Job `daily-snapshot`** (toutes les 24h) :
1. Traite les invalidations en attente
2. Recalcule hier (consolide les transactions de la veille)
3. Calcule le nouveau jour entrant dans la fenêtre (`today + horizon`)
---
### `GET /accounts/{id}/snapshots`
Liste la valorisation totale du compte jour par jour.
**Paramètres de filtre**
| Paramètre | Type | Description |
|---|---|---|
| `from` | string | Date de début `YYYY-MM-DD` (optionnel, défaut : première date disponible) |
| `to` | string | Date de fin `YYYY-MM-DD` (optionnel, défaut : dernier jour calculé dans la fenêtre) |
**Réponse `200`** — triée par date ASC
```json
[
{ "date": "2026-06-01", "valeur": 2750.00 },
{ "date": "2026-06-02", "valeur": 2800.00 },
{ "date": "2026-07-13", "valeur": 2950.00 }
]
```
---
### `GET /accounts/{id}/snapshots/positions`
Liste les positions détaillées (par instrument) du compte jour par jour.
**Paramètres de filtre** — mêmes que `/snapshots` (`from`, `to`)
**Réponse `200`** — triée par date ASC puis instrument_id
```json
[
{
"date": "2026-06-01",
"instrument_id": 1,
"quantite": 2750.00,
"prix_cloture": 1.0,
"valeur": 2750.00
},
{
"date": "2026-06-01",
"instrument_id": 2,
"quantite": 5.0,
"pru": 350.00,
"prix_cloture": 360.00,
"valeur": 1800.00
}
]
```
---
### `GET /snapshots/pending`
Liste tous les comptes de l'owner ayant une invalidation en attente (i.e. snapshots non à jour suite à une mutation de transaction).
Utile pour savoir quels comptes doivent être recalculés avant de consulter leurs snapshots, ou pour déclencher un `recompute` ciblé.
**Réponse `200`** — liste vide si tout est à jour
```json
[
{ "account_id": 1, "recompute_from": "2026-06-10" },
{ "account_id": 3, "recompute_from": "2026-05-01" }
]
```
| Champ | Description |
|---|---|
| `account_id` | Identifiant du compte à recalculer |
| `recompute_from` | Date la plus ancienne depuis laquelle les snapshots sont invalides |
> Les invalidations sont insérées automatiquement à chaque create / update / delete / validate de transaction. Le job `daily-snapshot` les consomme toutes les 24h ; cet endpoint permet de les inspecter sans attendre.
---
### `POST /accounts/{id}/snapshots/recompute`
Recalcule immédiatement les snapshots d'un compte depuis sa **date d'invalidation** jusqu'à `today + horizon`.
Chaque mutation de transaction (create, update, delete, validate/dévalider) marque automatiquement les comptes concernés comme dirty avec `recompute_from = MIN(date_existante, date_transaction)`. Cet endpoint consomme ce flag sans attendre le job nocturne.
**Réponse `200`** — recalcul effectué
```json
{ "account_id": 1, "from": "2026-06-01", "to": "2026-07-13", "status": "ok" }
```
**Réponse `204`** — aucune invalidation en attente, rien à faire
**Réponse `404`** — compte introuvable