Premier commit

This commit is contained in:
2026-09-11 14:55:41 +02:00
commit 8ed04aae89
134 changed files with 36627 additions and 0 deletions
@@ -0,0 +1,21 @@
namespace SIFinance.Modules.Achats.Dtos;
public record BudgetRevisionDto(int Id, decimal MontantRevise, string? Commentaire, string RevisePar, DateTime DateRevision);
public record BudgetServiceDto(
int Id,
int ServiceId,
string Service,
int ExerciceAnnee,
decimal MontantInitial,
decimal? MontantRevise,
List<BudgetRevisionDto> Revisions);
// L'exercice n'est pas calendaire : ExerciceAnnee=2026 désigne la période du 01/09/2026
// au 31/08/2027 (cf. BudgetService.cs). Un budget initial est unique par service et par
// exercice — la création échoue s'il en existe déjà un (contrainte d'unicité en base).
public record CreateBudgetServiceDto(int ServiceId, int ExerciceAnnee, decimal MontantInitial);
// Chaque révision est ajoutée à l'historique, jamais écrasée — MontantRevise doit
// représenter le nouveau plafond total, pas un delta à ajouter.
public record CreateBudgetRevisionDto(decimal MontantRevise, string? Commentaire);
@@ -0,0 +1,57 @@
namespace SIFinance.Modules.Achats.Dtos;
public record ContratEcheanceDto(int Id, DateOnly Date, decimal Montant, string Statut, string? JustificatifNom);
public record ContratDocumentDto(int Id, DateOnly Date, string Tag, string Nom);
// Rapprochement facture ↔ contrat : permet de voir, sur la fiche contrat, quelles factures
// ont déjà été saisies contre lui (sans lien automatique aux échéances — le rapprochement
// avec une échéance précise reste une vérification humaine).
public record ContratFactureLieeDto(int Id, string Numero, decimal MontantTtc, DateOnly DateFacture, string Statut);
public record ContratDto(
int Id,
int FournisseurId,
string Fournisseur,
int? ServiceId,
string? Service,
int? CampusId,
string? Campus,
string? Reference,
string Objet,
DateOnly? DateSignature,
DateOnly DateDebut,
DateOnly DateFin,
DateOnly? DateResiliation,
string? MotifResiliation,
decimal MontantTotal,
string Periodicite,
string Statut,
List<ContratEcheanceDto> Echeances,
List<ContratDocumentDto> Documents,
List<ContratFactureLieeDto> FacturesLiees);
public record CreateContratEcheanceDto(DateOnly Date, decimal Montant);
public record CreateContratDto(
int FournisseurId,
int? ServiceId,
int? CampusId,
string Objet,
string? Reference,
DateOnly? DateSignature,
DateOnly DateDebut,
DateOnly DateFin,
decimal MontantTotal,
string Periodicite,
List<CreateContratEcheanceDto> Echeances);
public record ChangerStatutContratDto(string Statut, DateOnly? DateResiliation, string? MotifResiliation, string? Commentaire);
public record ContratHistoriqueDto(int Id, string? AncienStatut, string NouveauStatut, string? Commentaire, string Utilisateur, DateTime DateAction);
public record UpdateEcheanceDto(DateOnly Date, decimal Montant, string Statut, string? JustificatifNom);
public record AddDocumentDto(string Tag, string Nom, DateOnly? Date);
public record UpdateDocumentDto(string Tag, DateOnly Date);
@@ -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);
@@ -0,0 +1,41 @@
namespace SIFinance.Modules.Achats.Dtos;
public record CreateJustificatifDto(
string Libelle, string Nom,
string? SharepointUrl, string? SharepointItemId, long? TailleOctets);
public record DemandeAchatCreateDto(
int ServiceId,
string Objet,
string? Description,
int? FournisseurId,
string? FournisseurLibre,
string? CategorieFournisseurLibre,
int TypeDepenseId,
decimal MontantHt,
decimal TauxTva,
DateOnly DatePrevisionnelle,
int ModePaiementId,
List<CreateJustificatifDto>? Justificatifs);
public record UpdateDemandeStatutDto(string Statut);
// Le workflow matériel utilise des actions, plutôt qu'un statut libre envoyé par le
// navigateur : l'API conserve ainsi le contrôle des transitions et des rôles autorisés.
public record WorkflowDemandeDto(
string Action,
string? Commentaire,
string? DocumentNom = null,
string? DocumentSharepointUrl = null,
string? DocumentSharepointItemId = null,
long? DocumentTailleOctets = null);
public record AjouterFactureDemandeDto(
string Numero,
decimal Montant,
DateOnly DateFacture,
DateOnly DateEcheance,
string DocumentNom,
string DocumentSharepointUrl,
string DocumentSharepointItemId,
long DocumentTailleOctets);
@@ -0,0 +1,35 @@
namespace SIFinance.Modules.Achats.Dtos;
public record DemandeJustificatifDto(int Id, string Libelle, string Nom, DateTime? DateUpload, string? SharepointUrl);
public record DemandeWorkflowHistoriqueDto(string Action, string? Commentaire, string? AncienStatut, string? NouveauStatut, string Auteur, DateTime? DateAction);
public record DemandeAchatDto(
int Id,
string Reference,
int ServiceId,
string Objet,
string? Description,
int? FournisseurId,
string? FournisseurLibre,
string? CategorieFournisseurLibre,
int TypeDepenseId,
decimal MontantHt,
decimal TauxTva,
decimal MontantTva,
decimal MontantTtc,
DateOnly DatePrevisionnelle,
int ModePaiementId,
string Statut,
int DemandeurId,
string DemandeurNom,
int? ValideurId,
string? ValideurNom,
decimal SeuilApplicable,
DateTime? DateCreation,
DateTime? DateSoumission,
DateTime? DateDerniereMaj,
List<DemandeJustificatifDto> Justificatifs,
bool PeutValider,
bool FactureAjoutee = false,
string? FactureUrl = null,
string? FactureNom = null);
@@ -0,0 +1,17 @@
namespace SIFinance.Modules.Achats.Dtos;
public record ServiceDto(int Id, string Libelle);
public record TypeDepenseDto(int Id, string Libelle);
public record ModePaiementDto(int Id, string Libelle, bool NecessiteXml);
public record FournisseurDto(int Id, string RaisonSociale, string? Siret, string Statut);
public record FournisseurDetailDto(
int Id, string RaisonSociale, string? Siret, string? Email, string? Telephone,
string? Adresse, string Statut, DateTime? DateCreation);
public record FournisseurSaveDto(
string RaisonSociale, string? Siret, string? Email, string? Telephone,
string? Adresse, string Statut);