using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; using SIFinance.Modules.Achats.Dtos; using SIFinance.Shared; using SIFinance.Shared.Entities; namespace SIFinance.Modules.Achats.Endpoints; public static class BudgetsEndpoints { public static IEndpointRouteBuilder MapBudgetsEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/budgets").WithTags("Budgets").RequireAuthorization(); // exercice optionnel : sans filtre, remonte tous les exercices connus (utile pour // un historique) — Pilotage passera l'exercice courant pour ne charger que l'utile. group.MapGet("", async (int? exercice, SIFinanceDbContext db, CancellationToken ct) => { var query = db.BudgetsServices .AsNoTracking() .Include(b => b.Service) .Include(b => b.Revisions).ThenInclude(r => r.ReviseParUtilisateur) .AsQueryable(); if (exercice.HasValue) query = query.Where(b => b.ExerciceAnnee == exercice.Value); var budgets = await query.OrderBy(b => b.Service.Libelle).ToListAsync(ct); return budgets.Select(ToDto); }); group.MapPost("", async (CreateBudgetServiceDto input, SIFinanceDbContext db, CurrentUserService currentUser, CancellationToken ct) => { if (!await currentUser.HasRoleAsync("FINANCE", db, ct)) return Results.Forbid(); if (input.MontantInitial <= 0) return Results.BadRequest(new { error = "Le montant initial doit être supérieur à 0." }); var existeDeja = await db.BudgetsServices .AnyAsync(b => b.ServiceId == input.ServiceId && b.ExerciceAnnee == input.ExerciceAnnee, ct); if (existeDeja) return Results.BadRequest(new { error = "Un budget existe déjà pour ce service sur cet exercice." }); var budget = new BudgetService { ServiceId = input.ServiceId, ExerciceAnnee = input.ExerciceAnnee, MontantInitial = input.MontantInitial, SaisieParId = await currentUser.GetOrCreateCurrentUserIdAsync(ct), DateCreation = DateTime.UtcNow, }; db.BudgetsServices.Add(budget); await db.SaveChangesAsync(ct); await db.Entry(budget).Reference(b => b.Service).LoadAsync(ct); return Results.Created($"/api/budgets/{budget.Id}", ToDto(budget)); }); // Réservé à FINANCE : seul le responsable Finance qui gère le budget peut // l'augmenter en cours d'exercice. Chaque appel ajoute une ligne à l'historique // (budgets_service_revisions) plutôt que d'écraser une valeur unique. group.MapPost("/{id:int}/revisions", async (int id, CreateBudgetRevisionDto input, SIFinanceDbContext db, CurrentUserService currentUser, CancellationToken ct) => { if (!await currentUser.HasRoleAsync("FINANCE", db, ct)) return Results.Forbid(); var budget = await db.BudgetsServices .Include(b => b.Service) .Include(b => b.Revisions).ThenInclude(r => r.ReviseParUtilisateur) .FirstOrDefaultAsync(b => b.Id == id, ct); if (budget is null) return Results.NotFound(); if (input.MontantRevise <= 0) return Results.BadRequest(new { error = "Le montant révisé doit être supérieur à 0." }); var revision = new BudgetServiceRevision { BudgetServiceId = id, MontantRevise = input.MontantRevise, Commentaire = string.IsNullOrWhiteSpace(input.Commentaire) ? null : input.Commentaire.Trim(), RevisePar = await currentUser.GetOrCreateCurrentUserIdAsync(ct), DateRevision = DateTime.UtcNow, }; db.BudgetServiceRevisions.Add(revision); budget.DateDerniereMaj = DateTime.UtcNow; await db.SaveChangesAsync(ct); await db.Entry(revision).Reference(r => r.ReviseParUtilisateur).LoadAsync(ct); budget.Revisions.Add(revision); return Results.Created($"/api/budgets/{id}/revisions/{revision.Id}", ToDto(budget)); }); return app; } private static BudgetServiceDto ToDto(BudgetService b) { var revisions = b.Revisions .OrderByDescending(r => r.DateRevision) .Select(r => new BudgetRevisionDto( r.Id, r.MontantRevise, r.Commentaire, $"{r.ReviseParUtilisateur.Prenom} {r.ReviseParUtilisateur.Nom}".Trim(), r.DateRevision)) .ToList(); return new BudgetServiceDto( b.Id, b.ServiceId, b.Service.Libelle, b.ExerciceAnnee, b.MontantInitial, revisions.Count > 0 ? revisions[0].MontantRevise : null, revisions); } }