Aller au contenu principal

API Historique des Achats (ClientBuyHistory)

Module de gestion de l'historique des achats des clients pour le système de fidélisation.

Description

Ce module permet d'enregistrer et de gérer l'historique des achats effectués par les clients. Chaque achat est enregistré ligne par ligne (un article = une ligne dans la base de données), permettant une analyse détaillée des comportements d'achat.

Architecture

Traitement Asynchrone avec RabbitMQ

⚠️ Important: L'enregistrement des achats fonctionne de manière asynchrone via RabbitMQ.

Les applications externes publient directement dans RabbitMQ (pas d'endpoint POST HTTP).

Flux de traitement:

Application externe (Caisse, E-commerce, etc.)
|
| Publie un message via AMQP
v
Exchange RabbitMQ: "fidelite.historique-achat.exchange"
|
| Route vers
v
Queue RabbitMQ: "fidelite.historique-achat"
|
| Consumer écoute
v
Consumer NestJS (ClientBuyHistoryConsumer)
|
| Valide et traite
v
Base de données PostgreSQL

Configuration RabbitMQ:

  • Exchange: fidelite.historique-achat.exchange (type: direct)
  • Queue: fidelite.historique-achat
  • Routing Key: fidelite.historique-achat
  • Connexion: amqp://guest:guest@localhost:5672 (développement)

Avantages:

  • ✅ Découplage complet entre applications
  • ✅ Meilleure scalabilité
  • ✅ Résilience aux pics de charge
  • ✅ Pas de dépendance HTTP
  • ✅ RabbitMQ gère automatiquement la persistance des messages

Structure des fichiers

backend/src/client-buy-history/
├── dto/
│ ├── purchase-item.dto.ts # DTO pour un article du panier
│ ├── create-client-buy-history.dto.ts # DTO pour créer un achat
│ └── update-client-buy-history.dto.ts # DTO pour modifier un achat
├── client-buy-history.service.ts # Logique métier
├── client-buy-history.consumer.ts # Consumer RabbitMQ
├── client-buy-history-public.controller.ts # Endpoints publics
├── client-buy-history-private.controller.ts # Endpoints privés
└── client-buy-history.module.ts # Module NestJS

Schéma de la table

Table : client_buy_history

ChampTypeDescription
idbigintIdentifiant unique
clientIdbigintRéférence au client (FK)
articleIdtextIdentifiant de l'article
articleNametextNom de l'article
quantitybigintQuantité achetée
categorytextCatégorie (optionnelle)
unitPricebigintPrix unitaire (en centimes)
totalAmountbigintMontant total de l'article (quantity × unitPrice)
loyaltyPointsEarnedbigintPoints de fidélité gagnés (0 pour l'instant)
purchaseDatetimestampDate et heure de l'achat
createdAttimestampDate de création de l'enregistrement
updatedAttimestampDate de dernière modification
deletedAttimestampDate de suppression (soft delete)

Endpoints

Publication dans RabbitMQ (Applications externes)

⚠️ Important: Il n'y a pas d'endpoint POST HTTP. Les applications doivent publier directement dans RabbitMQ.

Comment publier un message

Les applications externes doivent publier un message dans l'exchange RabbitMQ avec le format suivant :

Configuration de publication:

  • Exchange: fidelite.historique-achat.exchange
  • Routing Key: fidelite.historique-achat
  • Content-Type: application/json
  • Delivery Mode: persistent (2)

Format du message:

{
"action": "create",
"timestamp": "2025-10-23T15:37:39.512Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"clientId": "CLI_0001",
"purchaseDate": "2025-10-23T14:30:00Z",
"purchases": [
{
"articleId": "ART001",
"articleName": "Pomme",
"unitPrice": 250,
"quantity": 3,
"category": "Fruits"
},
{
"articleId": "ART002",
"articleName": "Pain",
"unitPrice": 150,
"quantity": 1,
"category": "Boulangerie"
}
]
}
}

Champs obligatoires:

  • action: Type d'action (toujours "create" pour l'instant)
  • timestamp: Date/heure de l'événement (format ISO 8601)
  • requestId: UUID unique pour tracer la requête
  • data.clientId: Identifiant du client
  • data.purchaseDate: Date/heure de l'achat
  • data.purchases: Tableau d'articles achetés (minimum 1)

Note: Le totalAmount de chaque article est calculé automatiquement par le consumer (quantity × unitPrice). category est optionnel. S'il est absent, la catégorie sera NULL en base.

Traitement du message:

  1. Le message est routé vers la queue fidelite.historique-achat
  2. Le consumer NestJS récupère le message
  3. Il valide les données et vérifie que le client existe
  4. Il insère les achats en base de données
  5. Il ACK le message (succès) ou NACK (erreur)

En cas d'erreur:

  • Le message est rejeté (NACK)
  • Il est jeté (pas de DLQ configurée actuellement)
  • L'erreur est loggée avec le requestId dans les logs du serveur

Endpoints publics

Base URL : /api/publique/client-buy-history

GET /:clientId - Récupérer les achats d'un client

Récupère l'historique complet des achats d'un client spécifique avec pagination et filtres.

Paramètres URL:

  • clientId (required) : ID du client

Query parameters:

  • page (optional, default: 1) : Numéro de page
  • limit (optional, default: 10) : Nombre d'éléments par page
  • dateFrom (optional) : Date de début (format ISO 8601)
  • dateTo (optional) : Date de fin (format ISO 8601)
  • minAmount (optional) : Montant minimum en centimes
  • maxAmount (optional) : Montant maximum en centimes
  • articleId (optional) : Filtrer par identifiant d'article
  • articleName (optional) : Filtrer par nom d'article (recherche partielle)

Exemple:

GET /api/publique/client-buy-history/123?page=1&limit=10&articleName=Pomme

Réponse (200):

{
"data": [
{
"id": 1,
"clientId": 123,
"articleId": "ART001",
"articleName": "Pomme",
"quantity": 3,
"unitPrice": 250,
"totalAmount": 750,
"loyaltyPointsEarned": 0,
"purchaseDate": "2025-10-23T14:30:00.000Z",
"createdAt": "2025-10-23T15:37:39.512Z",
"updatedAt": "2025-10-23T15:37:39.512Z",
"deletedAt": null
}
],
"meta": {
"total": 1,
"page": 1,
"limit": 10,
"totalPages": 1
}
}

Habitudes d'achats (12 mois)

Base URL : /api/publique/clients/:id/habits

Retourne les catégories et articles les plus achetés par le client sur les 12 derniers mois.

Query params optionnels:

  • limitItems (défaut: 5)
  • limitCategories (défaut: 5)

Réponse (200):

{
"topCategories": [
{ "category": "Fruits", "totalQuantity": 42, "totalAmount": 10500 },
{ "category": "Boulangerie", "totalQuantity": 21, "totalAmount": 6300 }
],
"topItems": [
{
"articleId": "ART001",
"articleName": "Pomme",
"totalQuantity": 30,
"totalAmount": 7500
},
{
"articleId": "ART010",
"articleName": "Baguette",
"totalQuantity": 18,
"totalAmount": 5400
}
],
"meta": {
"period": "last_12_months",
"asOf": "2025-11-11T10:00:00.000Z",
"limits": { "limitItems": 5, "limitCategories": 5 }
}
}

Endpoints privés (Admin)

Base URL : /api/privee/client-buy-history

POST / - Créer un historique d'achat (sans RabbitMQ)

Crée directement un historique d'achat pour un client dans la base de données, sans passer par RabbitMQ.

⚠️ Note: Cet endpoint est réservé aux administrateurs pour créer des achats manuellement. Les applications externes doivent utiliser RabbitMQ (voir section "Publication dans RabbitMQ").

Body (obligatoire):

{
"clientId": "CLI_0001",
"purchaseDate": "2025-10-24T10:30:00Z",
"purchases": [
{
"articleId": "ART001",
"articleName": "Pomme",
"unitPrice": 250,
"quantity": 3
},
{
"articleId": "ART002",
"articleName": "Banane",
"unitPrice": 180,
"quantity": 2
}
]
}

Champs obligatoires:

  • clientId: Identifiant métier du client (ex: "CLI_0001")
  • purchaseDate: Date/heure de l'achat au format ISO 8601
  • purchases: Tableau d'articles achetés (minimum 1 article)
    • articleId: Identifiant de l'article
    • articleName: Nom de l'article
    • unitPrice: Prix unitaire en centimes
    • quantity: Quantité achetée (> 0)

Exemple de requête:

curl -X POST http://localhost:3000/api/privee/client-buy-history \
-H "Content-Type: application/json" \
-d '{
"clientId": "CLI_0001",
"purchaseDate": "2025-10-24T10:30:00Z",
"purchases": [
{
"articleId": "ART001",
"articleName": "Pomme",
"unitPrice": 250,
"quantity": 3
}
]
}'

Réponse (201):

{
"message": "2 article(s) ajouté(s) avec succès",
"data": [
{
"id": 1,
"clientId": 123,
"articleId": "ART001",
"articleName": "Pomme",
"quantity": 3,
"unitPrice": 250,
"totalAmount": 750,
"loyaltyPointsEarned": 0,
"purchaseDate": "2025-10-24T10:30:00.000Z",
"createdAt": "2025-10-24T16:54:20.512Z",
"updatedAt": "2025-10-24T16:54:20.512Z",
"deletedAt": null
},
{
"id": 2,
"clientId": 123,
"articleId": "ART002",
"articleName": "Banane",
"quantity": 2,
"unitPrice": 180,
"totalAmount": 360,
"loyaltyPointsEarned": 0,
"purchaseDate": "2025-10-24T10:30:00.000Z",
"createdAt": "2025-10-24T16:54:20.512Z",
"updatedAt": "2025-10-24T16:54:20.512Z",
"deletedAt": null
}
]
}

Erreurs:

  • 400 : Données invalides (champs manquants ou format incorrect)
  • 404 : Client non trouvé

Différence avec RabbitMQ:

CritèrePOST /api/priveeRabbitMQ
TraitementSynchroneAsynchrone
RéponseImmédiateDifférée
UtilisationInterface adminIntégrations
RésilienceDépend du serveurTrès haute
DécouplageNonOui

GET / - Récupérer tous les achats

Récupère l'historique complet des achats de tous les clients avec pagination et filtres.

Query parameters: (mêmes que l'endpoint public GET /:clientId)

Exemple:

GET /api/privee/client-buy-history?page=1&limit=10&minAmount=500

Réponse (200): (même format que l'endpoint public)


GET /:id - Récupérer un achat spécifique

Récupère les détails d'un achat spécifique par son ID.

Paramètres URL:

  • id (required) : ID de l'achat

Exemple:

GET /api/privee/client-buy-history/1

Réponse (200):

{
"id": 1,
"clientId": 123,
"articleId": "ART001",
"articleName": "Pomme",
"quantity": 3,
"unitPrice": 250,
"totalAmount": 750,
"loyaltyPointsEarned": 0,
"purchaseDate": "2025-10-23T14:30:00.000Z",
"createdAt": "2025-10-23T15:37:39.512Z",
"updatedAt": "2025-10-23T15:37:39.512Z",
"deletedAt": null
}

Erreurs:

  • 404 : L'achat n'existe pas

PATCH /:id - Modifier un achat

Met à jour les informations d'un achat spécifique (article, quantité, prix).

Paramètres URL:

  • id (required) : ID de l'achat

Body (tous les champs sont optionnels):

{
"articleId": "ART001",
"articleName": "Pomme Rouge",
"unitPrice": 300,
"quantity": 5
}

Réponse (200):

{
"id": 1,
"clientId": 123,
"articleId": "ART001",
"articleName": "Pomme Rouge",
"quantity": 5,
"unitPrice": 300,
"totalAmount": 1500,
"loyaltyPointsEarned": 0,
"purchaseDate": "2025-10-23T14:30:00.000Z",
"createdAt": "2025-10-23T15:37:39.512Z",
"updatedAt": "2025-10-23T15:37:58.498Z",
"deletedAt": null
}

Erreurs:

  • 404 : L'achat n'existe pas
  • 400 : Données invalides

DELETE /:id - Supprimer un achat (soft delete)

Supprime logiquement un achat (le marque comme supprimé sans le retirer de la base).

Paramètres URL:

  • id (required) : ID de l'achat

Exemple:

DELETE /api/privee/client-buy-history/1

Réponse (200):

{
"message": "L'achat 1 a été supprimé avec succès"
}

Erreurs:

  • 404 : L'achat n'existe pas

Validation des données

PurchaseItemDto

ChampTypeContraintes
articleIdstringRequis, non vide
articleNamestringRequis, non vide
unitPricenumberRequis, >= 0
quantitynumberRequis, > 0
categorystringOptionnel (ex: "Fruits")

CreateClientBuyHistoryDto

ChampTypeContraintes
clientIdnumberRequis, > 0
purchaseDatestring (ISO 8601)Requis, format ISO 8601
purchasesPurchaseItemDto[]Requis, tableau avec au moins 1 élément

Règles métier

  1. Validation du client : Avant de créer un achat, le système vérifie que le clientId existe dans la table clients.

  2. Points de fidélité : Pour l'instant, loyaltyPointsEarned est toujours mis à 0. Une règle métier pourra être ajoutée ultérieurement.

  3. Soft delete : Les achats supprimés ne sont pas physiquement retirés de la base mais marqués avec un deletedAt. Ils n'apparaissent plus dans les résultats de requête.

  4. Calcul automatique : Le totalAmount de chaque ligne est calculé automatiquement comme quantity × unitPrice. Chaque ligne représente un article distinct.

  5. Tri par défaut : Les résultats sont toujours triés par purchaseDate en ordre décroissant (du plus récent au plus ancien).

Exemples d'utilisation

Cas 1 : Enregistrer un achat en caisse

curl -X POST http://localhost:3000/api/publique/client-buy-history \
-H "Content-Type: application/json" \
-d '{
"clientId": 123,
"purchaseDate": "2025-10-23T14:30:00Z",
"purchases": [
{"articleId": "ART001", "articleName": "Pomme", "unitPrice": 250, "quantity": 3},
{"articleId": "ART002", "articleName": "Pain", "unitPrice": 150, "quantity": 1},
{"articleId": "ART003", "articleName": "Lait", "unitPrice": 120, "quantity": 5}
]
}'

Cas 2 : Consulter l'historique d'un client

curl -X GET "http://localhost:3000/api/publique/client-buy-history/123?page=1&limit=20"

Cas 3 : Rechercher tous les achats de pommes

curl -X GET "http://localhost:3000/api/privee/client-buy-history?articleName=Pomme"

Cas 4 : Corriger une erreur de saisie

curl -X PATCH http://localhost:3000/api/privee/client-buy-history/1 \
-H "Content-Type: application/json" \
-d '{"quantity": 5, "unitPrice": 300}'

Cas 5 : Annuler un achat erroné

curl -X DELETE http://localhost:3000/api/privee/client-buy-history/1

Surveillance et logs

Le service utilise le système de logging NestJS avec le préfixe [FIDELITE][ClientBuyHistoryService] et [FIDELITE][ClientBuyHistory*Controller].

Exemples de logs :

  • Création de l'historique d'achat pour le client 123
  • 2 article(s) ajouté(s) à l'historique
  • Récupération des achats du client 123 avec options: {...}
  • Achat 1 mis à jour avec succès
  • Achat 1 supprimé avec succès

Documentation Swagger

La documentation Swagger complète est disponible à l'URL : http://localhost:649/api/swagger

Les endpoints sont regroupés sous deux tags :

  • Historique des achats (Public)
  • Historique des achats (Privé)

Notes techniques

  • Pagination : Implémentée avec les paramètres page et limit. Les métadonnées incluent total, page, limit, et totalPages.

  • Filtres : Tous les filtres sont cumulatifs (AND). Le filtre articleName utilise ILIKE pour une recherche insensible à la casse.

  • Soft delete : Utilise le champ deletedAt de la table. Tous les SELECT incluent automatiquement WHERE deletedAt IS NULL.

  • Type safety : Utilise les types inférés de Drizzle ORM (typeof clientBuyHistoryTable.$inferSelect).

  • DrizzleService : Utilise la méthode getDb() pour accéder à l'instance de base de données.

Évolutions futures possibles

  1. Calcul automatique des points de fidélité selon le niveau du client et le montant
  2. Export des achats au format CSV/Excel
  3. Statistiques d'achat (top articles, fréquence d'achat, etc.)
  4. Recommandations basées sur l'historique
  5. Notifications pour les achats suspects ou inhabituels
  6. Intégration avec un système de caisse via webhook

Support

Pour toute question ou problème, consulter :

  • La documentation Swagger : http://localhost:649/api/swagger
  • Les logs du backend
  • Le fichier CLAUDE.md à la racine du projet