Premier commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SIFinance.Shared.Entities;
|
||||
|
||||
namespace SIFinance.Modules.Achats.Endpoints;
|
||||
|
||||
// Endpoint agrégé du tableau de bord : tous les compteurs et sommes sont calculés
|
||||
// par SQL Server (COUNT/SUM), et seules les quelques lignes réellement affichées
|
||||
// sont transférées. Le temps de réponse ne dépend donc plus du volume en base.
|
||||
public static class DashboardEndpoints
|
||||
{
|
||||
private static readonly string[] StatutsEnAttente = ["SOUMISE", "EN_VALIDATION", "COMPLEMENT_REQUIS"];
|
||||
private static readonly string[] StatutsEngages = ["VALIDEE", "FACTUREE"];
|
||||
|
||||
public static IEndpointRouteBuilder MapDashboardEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/dashboard").WithTags("Dashboard").RequireAuthorization();
|
||||
|
||||
group.MapGet("", async (SIFinanceDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var today = DateTime.UtcNow.Date;
|
||||
// DateEcheance est un DateOnly côté entité : les comparaisons doivent se
|
||||
// faire avec un DateOnly pour être traduites en SQL.
|
||||
var todayDate = DateOnly.FromDateTime(today);
|
||||
|
||||
// Bornes du trimestre courant et du précédent, calculées côté C# pour que
|
||||
// la comparaison reste une simple comparaison de dates traduisible en SQL.
|
||||
var trimCourant = (today.Month - 1) / 3;
|
||||
var debutTrimestre = new DateTime(today.Year, trimCourant * 3 + 1, 1);
|
||||
var debutTrimestrePrecedent = debutTrimestre.AddMonths(-3);
|
||||
|
||||
var demandes = db.DemandesAchats.AsNoTracking();
|
||||
var factures = db.Factures.AsNoTracking();
|
||||
|
||||
// --- KPI demandes ---
|
||||
var enAttente = demandes.Where(d => StatutsEnAttente.Contains(d.Statut));
|
||||
var nbAValider = await enAttente.CountAsync(ct);
|
||||
var montantAValider = await enAttente.SumAsync(d => (decimal?)d.MontantTtc, ct) ?? 0m;
|
||||
|
||||
var engagees = demandes.Where(d => StatutsEngages.Contains(d.Statut));
|
||||
var engageCourant = await engagees
|
||||
.Where(d => d.DateCreation >= debutTrimestre)
|
||||
.SumAsync(d => (decimal?)d.MontantTtc, ct) ?? 0m;
|
||||
var engagePrecedent = await engagees
|
||||
.Where(d => d.DateCreation >= debutTrimestrePrecedent && d.DateCreation < debutTrimestre)
|
||||
.SumAsync(d => (decimal?)d.MontantTtc, ct) ?? 0m;
|
||||
|
||||
// --- KPI factures ---
|
||||
var aPayer = factures.Where(f => f.Statut == "validee");
|
||||
var nbAPayer = await aPayer.CountAsync(ct);
|
||||
var montantAPayer = await aPayer.SumAsync(f => (decimal?)f.MontantTtc, ct) ?? 0m;
|
||||
|
||||
var enCours = factures.Where(f => f.Statut != "payee");
|
||||
var nbEchues = await enCours.CountAsync(f => f.DateEcheance < todayDate, ct);
|
||||
var nbProches = await enCours.CountAsync(f => f.DateEcheance >= todayDate && f.DateEcheance <= todayDate.AddDays(7), ct);
|
||||
|
||||
// --- Listes affichées (volumes bornés par Take) ---
|
||||
var demandesRecentes = await demandes
|
||||
.OrderByDescending(d => d.DateCreation)
|
||||
.Take(4)
|
||||
.Select(d => new DashboardDemandeDto(d.Id, d.Reference, d.Objet, d.MontantTtc, d.Statut, d.TypeDepenseId))
|
||||
.ToListAsync(ct);
|
||||
|
||||
var demandesAValiderApercu = await enAttente
|
||||
.OrderByDescending(d => d.DateCreation)
|
||||
.Take(1)
|
||||
.Select(d => new DashboardDemandeDto(d.Id, d.Reference, d.Objet, d.MontantTtc, d.Statut, d.TypeDepenseId))
|
||||
.ToListAsync(ct);
|
||||
|
||||
var echeances = await enCours
|
||||
.OrderBy(f => f.DateEcheance)
|
||||
.Take(5)
|
||||
.Select(f => new DashboardEcheanceDto(f.Id, f.NumeroFacture, f.Fournisseur.RaisonSociale, f.MontantTtc, f.DateEcheance))
|
||||
.ToListAsync(ct);
|
||||
|
||||
// Alertes : uniquement les premières lignes réellement affichées.
|
||||
var facturesEchues = await enCours
|
||||
.Where(f => f.DateEcheance < todayDate)
|
||||
.OrderBy(f => f.DateEcheance)
|
||||
.Take(5)
|
||||
.Select(f => new DashboardEcheanceDto(f.Id, f.NumeroFacture, f.Fournisseur.RaisonSociale, f.MontantTtc, f.DateEcheance))
|
||||
.ToListAsync(ct);
|
||||
|
||||
var avoirsDisponibles = await db.Avoirs
|
||||
.AsNoTracking()
|
||||
.Where(a => a.SoldeDisponible > 0)
|
||||
.OrderByDescending(a => a.DateCreation)
|
||||
.Take(5)
|
||||
.Select(a => new DashboardAvoirDto(a.Id, a.NumeroAvoir, a.Fournisseur.RaisonSociale, a.SoldeDisponible))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return Results.Ok(new DashboardDto(
|
||||
engageCourant, engagePrecedent,
|
||||
nbAValider, montantAValider,
|
||||
nbAPayer, montantAPayer,
|
||||
nbProches, nbEchues,
|
||||
demandesRecentes,
|
||||
demandesAValiderApercu.FirstOrDefault(),
|
||||
facturesEchues,
|
||||
avoirsDisponibles,
|
||||
echeances));
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
public record DashboardDemandeDto(int Id, string Reference, string Objet, decimal MontantTtc, string Statut, int TypeDepenseId);
|
||||
public record DashboardEcheanceDto(int Id, string Numero, string Fournisseur, decimal MontantTtc, DateOnly DateEcheance);
|
||||
public record DashboardAvoirDto(int Id, string Numero, string Fournisseur, decimal SoldeDisponible);
|
||||
|
||||
public record DashboardDto(
|
||||
decimal EngageCourant,
|
||||
decimal EngagePrecedent,
|
||||
int NbAValider,
|
||||
decimal MontantAValider,
|
||||
int NbAPayer,
|
||||
decimal MontantAPayer,
|
||||
int NbEcheancesProches,
|
||||
int NbFacturesEchues,
|
||||
IReadOnlyList<DashboardDemandeDto> DemandesRecentes,
|
||||
DashboardDemandeDto? PremiereAValider,
|
||||
IReadOnlyList<DashboardEcheanceDto> FacturesEchues,
|
||||
IReadOnlyList<DashboardAvoirDto> AvoirsDisponibles,
|
||||
IReadOnlyList<DashboardEcheanceDto> Echeances);
|
||||
Reference in New Issue
Block a user