Gestion des Commandes
Ce document présente l’architecture, la logique métier et la spécification des API du module de gestion des commandes (apps/api/src/orders). Ce module centralise les processus d’achat, de validation de commande, de décrémentation des stocks et de gestion du cycle de vie des commandes.
1. Modèle de Données et Relations
Le cycle de commande repose sur les entités Order et OrderItem définies dans le schéma Prisma, liant l’acheteur (User), l’adresse de livraison (Address) et les produits commandés (Product).
Description des Champs Principaux
Order:id(String, UUID) : Identifiant unique de la commande.buyerId(String) : Identifiant de l’utilisateur acheteur.shippingAddressId(String) : Identifiant de l’adresse de livraison associée.status(OrderStatus) : Statut de la commande (PENDING,CONFIRMED,SHIPPED,DELIVERED,CANCELLED).currencyCode(String, défaut"XAF") : Devise figée au moment de la commande, dérivée duCountryde l’adresse de livraison à cet instant.subtotal(Decimal) : Somme des lignes (quantité × prix unitaire), avant frais de port et TVA.shippingFee(Decimal) : Frais de port, figés depuisCountry.shippingFeeau moment de la commande.vatAmount(Decimal) : Montant de TVA, figé depuisCountry.vatRateau moment de la commande.totalAmount(Decimal) : Montant total =subtotal + shippingFee + vatAmount. Nom conservé tel quel — c’est ce que lit le module de paiement (CinetPay).
OrderItem:id(String, UUID) : Identifiant unique de la ligne de commande.orderId(String) : Identifiant de la commande parente.productId(String) : Identifiant du produit commandé.quantity(Int) : Quantité commandée.unitPrice(Decimal) : Prix unitaire du produit au moment de l’achat (historisé).
2. Rôles et Autorisations (RBAC)
Les endpoints de gestion des commandes appliquent des restrictions d’accès précises basées sur le rôle et la participation de l’utilisateur à la commande :
| Endpoint | Méthode | Rôle Requis | Description / Règle métier |
|---|---|---|---|
/orders | POST | Acheteur (BUYER) | Passer une commande (déduit le stock des produits commandés). |
/orders | GET | Tous (filtré) | Acheteur : Liste ses achats. Vendeur : Liste les commandes contenant ses produits. Admin : Liste tout. |
/orders/:id | GET | Acheteur | Vendeur | Admin | Consulter une commande spécifique (accès restreint aux personnes concernées). |
/orders/:id/status | PATCH | Vendeur | Admin | Mettre à jour le statut de la commande (ex: passer à CANCELLED ou CONFIRMED). |
[!IMPORTANT] Isolation des Vendeurs : Un vendeur ne peut modifier le statut d’une commande ou y accéder que si la commande contient au moins un produit issu de sa boutique. Le système effectue cette validation de manière stricte à chaque requête.
3. Logiques Métier Clés et Flux
A. Flux de Création d’une Commande (POST /orders)
Lors de la création d’une commande par un acheteur, l’API exécute ces étapes dans une transaction Prisma :
- Vérification que l’adresse de livraison existe et appartient bien à l’acheteur.
- Pour chaque produit du panier :
- Vérification de l’existence et du statut actif du produit.
- Contrôle du stock disponible. Si la quantité demandée est supérieure au stock, la transaction échoue (
400 Bad Request). - Décrémentation du stock du produit.
- Enregistrement du prix unitaire courant du produit.
- Calcul du montant total.
- Création de l’enregistrement de la commande (
Order) et de ses lignes (OrderItem).
B. Algorithme d’Annulation et Restauration des Stocks
Lorsqu’un vendeur ou un administrateur annule une commande (passage du statut à CANCELLED), les produits commandés doivent être restitués dans l’inventaire du magasin.
L’opération est encapsulée dans une transaction :
- Contrôle du statut actuel : impossible d’annuler une commande déjà livrée (
DELIVERED) ou déjà annulée (CANCELLED). - Pour chaque ligne de la commande, le service incrémente à nouveau la quantité sur la table
Product. - Le statut de l’ordre est mis à jour à
CANCELLED.
Code de transaction (dans orders.service.ts) :
return this.prisma.$transaction(async (tx) => {
// Si le statut passe à CANCELLED, on restitue les stocks
if (dto.status === OrderStatus.CANCELLED) {
for (const item of order.items) {
await tx.product.update({
where: { id: item.productId },
data: {
stock: {
increment: item.quantity,
},
},
});
}
}
await tx.order.update({
where: { id },
data: { status: dto.status },
});
// ...
});4. Guide des Endpoints et Exemples de Payloads
Création d’une Commande (POST /orders)
- Requête :
{ "shippingAddressId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "items": [ { "productId": "7bf3b934-8c81-42ab-ba0c-99d9b62fb91e", "quantity": 2 } ] } - Réponse (
201 Created) :{ "id": "ord-8888-uuid-example", "buyerId": "usr-buyer-uuid", "shippingAddressId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "status": "PENDING", "currencyCode": "XAF", "subtotal": 1000.00, "shippingFee": 50.00, "vatAmount": 192.50, "totalAmount": 1242.50, "createdAt": "2026-06-24T23:00:00.000Z", "updatedAt": "2026-06-24T23:00:00.000Z", "items": [ { "id": "item-001-uuid", "orderId": "ord-8888-uuid-example", "productId": "7bf3b934-8c81-42ab-ba0c-99d9b62fb91e", "quantity": 2, "unitPrice": 500.00, "product": { "id": "7bf3b934-8c81-42ab-ba0c-99d9b62fb91e", "name": "PlayStation 5 Slim", "price": "500.00", "stock": 3 } } ] }
Modification du Statut (PATCH /orders/:id/status)
- Requête (Vendeur / Admin uniquement) :
{ "status": "CONFIRMED" } - Réponse (
200 OK) :{ "id": "ord-8888-uuid-example", "buyerId": "usr-buyer-uuid", "shippingAddressId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "status": "CONFIRMED", "currencyCode": "XAF", "subtotal": 1000.00, "shippingFee": 50.00, "vatAmount": 192.50, "totalAmount": 1242.50, "createdAt": "2026-06-24T23:00:00.000Z", "updatedAt": "2026-06-24T23:10:00.000Z" }