#57 — Système de Tickets RMA
PLANIFIÉ
Priorité: 🟠 HAUTE · Type: TYPE B · Conteneur: rgz-api · Code: app/api/v1/endpoints/rma.pyDépendances: #1 rgz-api
Description
Le système RMA (Return Merchandise Authorization) gère le cycle complet des retours matériel CPE défectueux. Les revendeurs signalent les pannes, le NOC effectue un diagnostic à distance, approuve le retour, organise l'expédition, et clôt le dossier une fois le produit reçu en atelier. Le système déclenche automatiquement une alerte si le stock de pièces de rechange (spare) descend sous les 5 unités.
Chaque ticket suit un workflow à 5 états : SIGNALÉ (revendeur déclare panne) → DIAGNOSTIC (ingénieur NOC analyse) → APPROUVÉ (revendeur peut retourner) → EXPÉDIÉ (logistique en cours) → CLÔT (pièce testée, stock MAJ). L'historique complet et les photos de panne sont archivées pour amélioration produit et traçabilité garantie.
Architecture Interne
Workflow RMA : SIGNALÉ → DIAGNOSTIC → APPROUVÉ → EXPÉDIÉ → CLÔT
ÉTAT 1 - SIGNALÉ (T0)
Revendeur décrit panne via portail
Données: cpe_model, serial_number, failure_description, photo_fault_base64?, date_panne
Signature: POST /api/v1/rma { serial_number, symptom_code, description }
Stock: Pas d'action stock (ticket encore ouvert)
Notification: Email NOC + numéro ticket
ÉTAT 2 - DIAGNOSTIC (T1-T4)
Ingénieur NOC analyse distance:
- Vérifier historique sessions CPE (RADIUS logs)
- Chercher crashs, reconnexions, errors
- Demander logs WiFi revendeur (signal strength, interférences?)
- Cause probable: matériel (HW fault) vs config
Durée typique: 3-4 jours
Sortie: APPROUVÉ (c'est bien HW) ou REJETÉ (c'est SW)
Notification revendeur: SMS+email diagnostic + actions
ÉTAT 3 - APPROUVÉ (T5)
NOC approuve retour + génère RMA_NUMBER
Revendeur peut: ship CPE défectueuse à adresse RGZ
Étiquette: Imprimée via lien dans email (code-barres + RMA_NUMBER)
Stock spare: Décrémenté IMMÉDIATEMENT (reserve = 1 unité pour ce ticket)
Alerte: Si stock < 5, notifier DG + commander réapprov
ÉTAT 4 - EXPÉDIÉ (T6-T10)
Revendeur confirme expédition (photo colis + numéro suivi)
Data: carrier, tracking_number, shipped_at
Status: EN_TRANSIT (traçabilité logistique)
Notification: Email revendeur "Pièce reçue, atelier en diagnostic"
ÉTAT 5 - CLÔT (T11+)
Atelier RGZ reçoit CPE, teste:
- Batterie? Écran?
- Firmware crashe?
- Hardware défaut (composant)?
Conclusion: RÉPARABLE (HW fix, retour revendeur) | IRRÉPARABLE (déstockage)
Stock spare: Libérée si Irréparable
Notification revendeur: Statut final + options (remplacement, crédit)
Ticket archivé
TRIGGERS ALERTE STOCK:
Si spare_count ≤ 5 FCFA → SMS DG + email procurement
Exemple: stock Ubiquiti LiteBeam = 2 unités → "Urgence réappro 10x"Modèles de Données
class RmaStatus(str, Enum):
SIGNALÉ = "signale"
DIAGNOSTIC = "diagnostic"
APPROUVÉ = "approuve"
EXPÉDIÉ = "expedie"
CLÔT = "clot"
class RmaDiagnosis(str, Enum):
MATÉRIEL = "hardware"
FIRMWARE = "firmware"
CONFIGURATION = "configuration"
UTILISATEUR = "user_error"
# Table DB
rma_tickets:
- id UUID PK
- rma_number VARCHAR(16) UNIQUE (auto-generated: RMA-2026-000001)
- reseller_id UUID FK
- cpe_model VARCHAR(50) (ex: "Ubiquiti-LiteBeam-AC-M2")
- serial_number VARCHAR(50) UNIQUE
- failure_description TEXT
- photo_fault_url VARCHAR(255) NULL
- status CHECK(signale|diagnostic|approuve|expedie|clot)
- diagnosis_result VARCHAR(50) NULL (hardware|firmware|config|user_error)
- diagnosis_notes TEXT NULL
- spare_reserved BOOLEAN DEFAULT false
- carrier VARCHAR(50) NULL (ex: "DHL")
- tracking_number VARCHAR(100) NULL
- shipped_at TIMESTAMP NULL
- received_at TIMESTAMP NULL
- final_decision TEXT NULL (réparable|irréparable)
- created_at TIMESTAMP
- created_by UUID FK (revendeur)
- diagnosed_by UUID FK users NULL (ingénieur)
- diagnosed_at TIMESTAMP NULL
- resolved_at TIMESTAMP NULL
rma_stock_spares:
- id UUID PK
- cpe_model VARCHAR(50) UNIQUE FK cpe_models
- quantity_available INT
- quantity_reserved INT (count RMA tickets avec status=approuve|expedie)
- reorder_level INT DEFAULT 5
- last_restock_date TIMESTAMP
- supplier VARCHAR(100)
rma_audit_log:
- id UUID PK
- ticket_id UUID FK
- old_status VARCHAR(50)
- new_status VARCHAR(50)
- action TEXT
- created_by UUID FK users
- created_at TIMESTAMPConfiguration
# .env.example
RMA_NUMBER_PREFIX=RMA
RMA_DIAGNOSIS_TIMEOUT_DAYS=4
RMA_STOCK_ALERT_LEVEL=5
RMA_STOCK_ALERT_RECIPIENTS=dg@rgz.bj,procurement@rgz.bj
RMA_WAREHOUSE_ADDRESS="1234 Rue Commerce, Cotonou, Bénin"
RMA_SPARE_TRACKING_ENABLED=trueEndpoints API
| Méthode | Route | Requête | Réponse | Notes |
|---|---|---|---|---|
| POST | /api/v1/rma | 201 CREATED | ||
| GET | /api/v1/rma?reseller_id= | — | {items:[{id, rma_number, status, created_at}], total, page, pages} | 200 OK |
| GET | /api/v1/rma/ | — | 200 OK | |
| PUT | /api/v1/rma/{ticket_id}/diagnosis | 200 OK | ||
| PUT | /api/v1/rma/{ticket_id}/approve | — | 200 OK | |
| POST | /api/v1/rma/{ticket_id}/ship | 201 CREATED | ||
| PUT | /api/v1/rma/{ticket_id}/close | 200 OK | ||
| GET | /api/v1/rma/stock | — | 200 OK | |
| GET | /api/v1/rma/stock/ | — | 200 OK |
Règles de Sécurité (SEC)
- SEC-01 IDOR: Revendeur ne voit que ses propres tickets → vérifier
ticket.reseller_id == current_user.reseller_id - SEC-02 Immutabilité: Une fois CLÔT, ticket ne peut pas être modifié (sauf admin audit)
- SEC-03 Stock atomique: Réserver spare avec ACID (PostgreSQL transaction) pour éviter overbook
Commandes Utiles
# Signaler panne (revendeur)
curl -X POST http://localhost:8000/api/v1/rma \
-H "Authorization: Bearer REVENDEUR_TOKEN" \
-d '{
"serial_number": "LB-AC-M2-12345",
"cpe_model": "Ubiquiti-LiteBeam-AC-M2",
"description": "Appareil ne démarre plus après crash la nuit",
"photo_base64": "iVBORw0KGgo=..."
}'
# Vérifier ticket RMA revendeur
curl http://localhost:8000/api/v1/rma?reseller_id=550e \
-H "Authorization: Bearer REVENDEUR_TOKEN" | jq '.items'
# Consulter détail ticket
curl http://localhost:8000/api/v1/rma/550e8400-e29b-41d4-a716-446655440000
# Soumettre diagnostic (NOC ingénieur)
curl -X PUT http://localhost:8000/api/v1/rma/550e/diagnosis \
-H "Authorization: Bearer NOC_TOKEN" \
-d '{
"result": "hardware",
"notes": "Logs RADIUS: crash tous les 2h. Vraisemblablement défaut PSU."
}'
# Approuver RMA (NOC)
curl -X PUT http://localhost:8000/api/v1/rma/550e/approve \
-H "Authorization: Bearer NOC_TOKEN"
# Confirmer expédition (revendeur via mobile)
curl -X POST http://localhost:8000/api/v1/rma/550e/ship \
-H "Authorization: Bearer REVENDEUR_TOKEN" \
-d '{
"carrier": "DHL",
"tracking_number": "1234567890",
"photo_base64": "iVBORw0K..."
}'
# Clôturer ticket (atelier)
curl -X PUT http://localhost:8000/api/v1/rma/550e/close \
-H "Authorization: Bearer ATELIER_TOKEN" \
-d '{
"final_decision": "irreparable",
"notes": "Composant power management détruit, remplacement coûteux."
}'
# Vérifier stock spares (admin)
curl http://localhost:8000/api/v1/rma/stock \
-H "Authorization: Bearer ADMIN_TOKEN" | jq '.spare_count'
# Consommer spare pour nouveau CPE (approvisionnement)
curl http://localhost:8000/api/v1/rma/stock/Ubiquiti-LiteBeam-AC-M2 \
-H "Authorization: Bearer ADMIN_TOKEN"Intégration Avec Autres Outils
- #51 dashboard-reseller: Widget "Mes RMA" affiche tickets actifs + status
- #52 dashboard-noc: Vue "RMA Queue" filtre par status (diagnosis pending, etc)
- #63 email-notification: Notifie revendeur chaque transition
- #48 audit-trail: Chaque modification loggée
Implémentation TODO
- [ ] CRUD complet 5 états
- [ ] Auto-réservation spare à l'approbation
- [ ] Alerte stock < 5 (SMS DG)
- [ ] Génération étiquette PDF (code-barres)
- [ ] IDOR check revendeur ≠ ADMIN
- [ ] Audit trail (rma_audit_log)
- [ ] Notification SMS+email transitions
- [ ] Tests: créer ticket → approver → expédier → clôturer
- [ ] Dashboard RMA (historique, stats)
- [ ] Lien avec #52 dashboard-noc (queue RMA)
Dernière mise à jour: 2026-02-21