Réservations
Base URL : /api/v1/reservations
Les réservations permettent de réserver des repas ou snacks à l'avance. Le montant est débité immédiatement à la création.
Statuts
| Statut | Description |
|---|---|
pending | Réservée, non préparée |
ready | Prête à être retirée |
collected | Retirée par le porteur |
cancelled | Annulée (remboursée automatiquement) |
refunded | Remboursée |
uncollected | Non retirée |
Transitions autorisées
stateDiagram-v2
[*] --> pending: Création
pending --> ready: Préparation
pending --> collected: Retrait direct
pending --> cancelled: Annulation
ready --> collected: Retrait
ready --> cancelled: Annulation
collected --> [*]
cancelled --> [*]
POST /reservations
Crée une réservation (un ou plusieurs articles). Le montant est débité immédiatement.
Permissions : reservation.create ou reservation.manage
En-tête : Idempotency-Key (obligatoire — 422 si absente)
Si l'appelant est un porteur de carte, card_holder_id doit correspondre à son propre identifiant, sinon 403.
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
card_holder_id | uuid | Oui | Porteur bénéficiaire |
for_date | date | Oui | Date de la réservation |
items | array | Non* | Articles (product_id, quantity) — recommandé |
product_id | uuid | Oui* | Produit (mode mono-article hérité) |
quantity | integer | Oui* | Quantité (mode hérité) |
menu_id | uuid | Non | Menu associé |
site_id | uuid | Non | Site |
point_of_sale_id | uuid | Non | Point de vente |
notes | string | Non | Notes |
* product_id/quantity sont requis par la structure même si items est fourni.
POST /api/v1/reservations
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"card_holder_id": "uuid",
"for_date": "2025-06-21",
"items": [
{ "product_id": "uuid", "quantity": 1 }
]
}
Réponse (200)
{
"id": "uuid",
"card_holder_id": "uuid",
"product_id": "uuid",
"quantity": 1,
"unit_price_cents": 1200,
"unit_price": 1200,
"unit_price_formatted": "1 200 FCFA",
"total_price_cents": 1200,
"total_price": 1200,
"total_price_formatted": "1 200 FCFA",
"for_date": "2025-06-21",
"status": "pending",
"can_be_cancelled": true,
"card_transaction_id": "uuid",
"success": true,
"new_balance": 3800,
"notes": null,
"created_at": "2025-06-20T10:00:00Z",
"items": [
{
"id": "uuid",
"product_id": "uuid",
"quantity": 1,
"unit_price_cents": 1200,
"unit_price": 1200,
"unit_price_formatted": "1 200 FCFA",
"line_total_cents": 1200,
"line_total": 1200,
"line_total_formatted": "1 200 FCFA",
"currency": "XOF"
}
],
"currency": "XOF"
}
GET /reservations
Liste paginée des réservations.
Permissions : reservation.read ou reservation.manage
Paramètres de query
| Param | Type | Défaut | Description |
|---|---|---|---|
date | date | — | Filtrer par date (optionnel si search) |
site_id | uuid | — | Filtrer par site |
status | string | — | Filtrer par statut |
search | string | — | Code/nom porteur, notes |
limit | integer | 20 | Éléments par page |
offset | integer | 0 | Décalage |
Réponse (200)
{
"items": [
{
"id": "uuid",
"card_holder_id": "uuid",
"card_holder": {
"id": "uuid",
"first_name": "Fatou",
"last_name": "Diallo",
"establishment_name": null,
"code": "CL-EL-0008"
},
"product_id": "uuid",
"quantity": 1,
"unit_price_cents": 1200,
"unit_price": 1200,
"unit_price_formatted": "1 200 FCFA",
"total_price_cents": 1200,
"total_price": 1200,
"total_price_formatted": "1 200 FCFA",
"card_transaction_id": "uuid",
"for_date": "2025-06-21",
"site_id": "uuid",
"site_name": "Buvette Principale",
"menu_id": "uuid",
"performed_by_user_id": "uuid",
"performed_by_name": "Jean Mbeki",
"notes": null,
"status": "pending",
"can_be_cancelled": true,
"created_at": "2025-06-20T10:00:00Z",
"items": [
{
"id": "uuid",
"product_id": "uuid",
"product_name": "Riz au poisson",
"quantity": 1,
"unit_price_cents": 1200,
"unit_price": 1200,
"unit_price_formatted": "1 200 FCFA",
"line_total_cents": 1200,
"line_total": 1200,
"line_total_formatted": "1 200 FCFA",
"currency": "XOF"
}
]
}
],
"total": 12,
"currency": "XOF"
}
Dans la liste, chaque items[] inclut product_name (absent de la réponse de création).
PATCH /reservations/{id}/status
Change le statut d'une réservation.
Permissions :
cancelled→reservation.canceloureservation.manage- autres →
reservation.validateoureservation.manage
En-tête : Idempotency-Key (obligatoire)
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
status | string | Oui | "ready", "collected" ou "cancelled" |
reason | string | Non | Motif (conservé en cas d'annulation) |
PATCH /api/v1/reservations/{id}/status
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440002
Content-Type: application/json
{
"status": "ready"
}
Réponse (200)
Objet réservation enrichi, incluant refund_transaction_id et refund_reason en cas d'annulation.
Passer une réservation à cancelled déclenche automatiquement le remboursement sur la carte et la restauration du stock.
POST /reservations/multi-day
Crée plusieurs réservations (une par jour) avec une sélection de produits propre à chaque journée.
Permissions : reservation.create ou reservation.manage (avec contrôle IDOR)
En-tête : Idempotency-Key (obligatoire)
Corps de la requête
{
"card_holder_id": "uuid",
"site_id": "uuid",
"notes": "Semaine du 21 juin",
"days": [
{
"date": "2025-06-21",
"items": [{ "product_id": "uuid", "quantity": 1 }]
},
{
"date": "2025-06-22",
"items": [{ "product_id": "uuid", "quantity": 2 }]
}
]
}
Réponse (200)
{
"reservations": [
{
"id": "uuid",
"card_holder_id": "uuid",
"product_id": "uuid",
"quantity": 1,
"total_price_cents": 1200,
"total_price": 1200,
"total_price_formatted": "1 200 FCFA",
"for_date": "2025-06-21",
"status": "pending",
"can_be_cancelled": true,
"currency": "XOF"
}
],
"count": 2,
"currency": "XOF"
}
GET /reservations/by-card/{code}
Retourne les réservations du jour (statuts pending et ready) pour une carte scannée.
Permissions : reservation.read
Le paramètre code accepte indifféremment un code carte, un numéro de carte, un ancien hash QR ou un code porteur.
GET /api/v1/reservations/by-card/CL-EL-0008
Réponse (200)
{
"card": {
"id": "uuid",
"code": "CL-EL-0008",
"card_number": "6287940000123456",
"status": "active",
"balance_cents": 3800,
"balance": 3800,
"balance_formatted": "3 800 FCFA",
"currency": "XOF"
},
"holder": {
"id": "uuid",
"first_name": "Fatou",
"last_name": "Diallo",
"establishment_name": null,
"code": "CL-EL-0008"
},
"today": "2025-06-21",
"reservations": [ ],
"total": 0,
"currency": "XOF"
}
GET /reservations/availability
Vérifie si une date donnée est réservable et détaille la disponibilité de chaque produit.
Permissions : reservation.read
Toujours 200 — une date non réservable renvoie can_reserve: false avec un motif, sans erreur HTTP.
Paramètres de query
| Param | Type | Requis | Description |
|---|---|---|---|
date | date | Oui | Date à vérifier |
site_id | uuid | Non | Site |
Réponse (200)
{
"date": "2025-06-21",
"can_reserve": true,
"reason": "ok",
"message": null,
"has_menu": true,
"menu_id": "uuid",
"menu_name": "Menu du 21 juin",
"deadline": {
"min_advance_days": 1,
"earliest_date": "2025-06-21",
"creation_deadline_time": "09:00",
"max_reservation_days_ahead": 30,
"max_date": "2025-07-21"
},
"products": [
{
"product_id": "uuid",
"name": "Riz au poisson",
"code": "RZP",
"meal_type": "Fish",
"can_reserve": true,
"reason": "ok",
"message": null,
"reservation_deadline_days_before": 1,
"reservation_deadline_time": "09:00",
"is_active": true,
"available_on_date": true,
"stock_quantity": 50
}
]
}
Valeurs de reason (niveau date)
ok, past_date, below_min_advance, creation_deadline_passed, beyond_horizon, no_menu.
Valeurs de products[].reason (niveau produit)
ok, product_inactive, product_unavailable_day, product_deadline, product_deadline_time, product_out_of_stock.
GET /reservations/availability/range
Vérifie la disponibilité sur une plage de jours consécutifs.
Permissions : reservation.read
Paramètres de query
| Param | Type | Requis | Description |
|---|---|---|---|
start | date | Oui | Premier jour |
days | integer | Oui | Nombre de jours (1 à 366) |
site_id | uuid | Non | Site |
Réponse (200)
Un tableau d'objets DateAvailability (même forme que /availability), un par jour consécutif à partir de start.
[
{ "date": "2025-06-21", "can_reserve": true, "reason": "ok", "products": [] },
{ "date": "2025-06-22", "can_reserve": true, "reason": "ok", "products": [] }
]
Erreurs
| Code | Description |
|---|---|
422 | days hors bornes (0 ou > 366) |