import ActivityEvent from '#models/activity_event' import { computeKpis, computeTimeseries, topLatePayers, type RangeMonths, } from '#services/dashboard' import type { HttpContext } from '@adonisjs/core/http' import { Exception } from '@adonisjs/core/exceptions' import vine from '@vinejs/vine' const timeseriesValidator = vine.create({ range: vine.number().in([3, 6, 12]).optional(), }) const ACTIVITY_DEFAULT_LIMIT = 20 function requireOrgId(auth: HttpContext['auth']): string { const user = auth.getUserOrFail() if (!user.organizationId) { throw new Exception('Aucune organisation rattachée', { status: 404, code: 'not_found' }) } return user.organizationId } export default class DashboardController { /** * GET /dashboard/kpis * * Cf. service dashboard.ts — quelques metrics V1 sont placeholder * (miseEnDemeurePending=0 tant que RelanceTask pas branché, percentile * undefined tant que cohorte trop petite). */ async kpis({ auth, response }: HttpContext) { const organizationId = requireOrgId(auth) const data = await computeKpis(organizationId) return response.json({ data }) } /** * GET /dashboard/activity * * Journal append-only. Limit 20 par défaut, plus récent en tête. */ async activity({ auth, response }: HttpContext) { const organizationId = requireOrgId(auth) const events = await ActivityEvent.query() .where('organization_id', organizationId) .orderBy('at', 'desc') .limit(ACTIVITY_DEFAULT_LIMIT) return response.json({ data: events.map((e) => ({ id: e.id, kind: e.kind, at: e.at.toISO()!, label: e.label, meta: e.meta, })), }) } /** * GET /dashboard/top-late * * Top 5 clients avec le plus de factures en retard (status actif + * due_date dépassée). */ async topLate({ auth, response }: HttpContext) { const organizationId = requireOrgId(auth) const data = await topLatePayers(organizationId) return response.json({ data }) } /** * GET /dashboard/timeseries?range=6 * * Séries temporelles pour les graphes (encaissé mensuel + DSO mensuel * + pipeline par statut). Range : 3, 6 ou 12 mois (défaut 6). */ async timeseries({ auth, request, response }: HttpContext) { const organizationId = requireOrgId(auth) const { range } = await request.validateUsing(timeseriesValidator, { data: { range: request.input('range') ? Number(request.input('range')) : undefined, }, }) const data = await computeTimeseries(organizationId, (range ?? 6) as RangeMonths) return response.json({ data }) } }