rubis/bruno/07-Dashboard/01 KPIs.bru
ordinarthur 704f472729 feat(api): dashboard kpis + activity feed + top-late + ActivityEvent
Migration activity_events (uuid id, organization_id FK CASCADE, kind ENUM PG natif relance_sent/invoice_paid/invoice_imported/warning_drafted, at, label HTML léger, meta jsonb). Append-only — pas de mutation. Index (org, at).

Schema rules : kind typé en union + meta typé { invoiceId?, clientId?, planStepOrder? }.

Service activity_recorder.ts : recordActivity({orgId, kind, label, meta, trx?}). Branché dans :
- InvoicesController.markPaid → invoice_paid
- ImportBatchesController.validateDraft → invoice_imported
À venir : SendRelanceJob (relance_sent + warning_drafted) quand BullMQ sera là.

Service dashboard.ts :
- computeKpis(orgId) : 1 requête FILTER pour les counts par status + 1 requête pour les sommes paid this month / prev month / DSO. miseEnDemeurePending=0 et percentile=undefined V1 (placeholders honnêtes plutôt que faux chiffres).
- topLatePayers(orgId, 5) : INNER JOIN clients + agrégation count() par client_id, due_date < today + status actif.

Controller DashboardController :
- GET /dashboard/kpis : computeKpis
- GET /dashboard/activity : 20 derniers events de l'org, plus récent en tête
- GET /dashboard/top-late : top 5

Routes /api/v1/dashboard/* (auth requise).

Bruno : nouveau dossier 07-Dashboard avec 3 requêtes documentées.

Pour générer du contenu activity feed : encaisser une facture (Invoices → Mark paid) ou valider un draft (Imports → Validate). KPIs : créer des factures puis les marquer payées (paidAt rentre dans les sommes).
2026-05-06 15:10:58 +02:00

49 lines
1.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

meta {
name: 01 KPIs
type: http
seq: 1
}
get {
url: {{baseUrl}}/api/v1/dashboard/kpis
body: none
auth: inherit
}
tests {
test("200 OK", function () {
expect(res.getStatus()).to.equal(200);
});
test("KPIs shape", function () {
const k = res.getBody().data;
for (const key of [
"rubisCount", "rubisThisMonth", "hoursLiberatedThisMonth",
"encaisseCents", "encaisseDeltaCents",
"dsoDays", "dsoDeltaDays",
"factureToRelance", "factureInRelance", "factureNewToday",
"miseEnDemeurePending", "monthlyGoalProgress"
]) {
expect(k).to.have.property(key);
}
});
}
docs {
GET /api/v1/dashboard/kpis
Calculs agrégés sur les invoices de l'org :
- `rubisCount` lu sur Organization (compteur cumulé)
- `rubisThisMonth` = sum(rubis_earned where paid_at >= startOfMonth)
- `hoursLiberatedThisMonth` = rubisThisMonth × 10 (1 rubis = 10 min)
- `encaisseCents` = sum(amount_ttc_cents where paid this month)
- `encaisseDeltaCents` = ce mois mois précédent
- `dsoDays` = avg(paid_at issue_date) en jours, sur factures payées ce mois
- `dsoDeltaDays` = idem delta vs mois précédent
- `factureToRelance` = count(status='pending')
- `factureInRelance` = count(status='in_relance')
- `factureNewToday` = count(created_at >= today)
- `miseEnDemeurePending` = 0 (à brancher quand RelanceTask sera là)
- `monthlyGoalProgress` = clamp(rubisThisMonth/25*100, 0, 100) — placeholder
- `percentile` = undefined V1
}