Skip to content

#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

python
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 TIMESTAMP

Configuration

env
# .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=true

Endpoints API

MéthodeRouteRequêteRéponseNotes
POST/api/v1/rma201 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}/diagnosis200 OK
PUT/api/v1/rma/{ticket_id}/approve200 OK
POST/api/v1/rma/{ticket_id}/ship201 CREATED
PUT/api/v1/rma/{ticket_id}/close200 OK
GET/api/v1/rma/stock200 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

bash
# 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

PROJET MOSAÏQUE — 81 outils, 22 conteneurs, 500+ revendeurs WiFi Zone