using System.Net.Mime; using Microsoft.AspNetCore.Mvc; using SchotzIadKmTracker.Api.Services; using SchotzIadKmTracker.Api.Services.Exceptions; namespace SchotzIadKmTracker.Api.Controllers; /// /// Endpoints d'Export_PDF : rapport mensuel et synthèse annuelle (Exigences 7.3, 7.4, 7.5). /// Chaque réponse renvoie un unique fichier application/pdf avec l'en-tête /// Content-Disposition: attachment et le nom de fichier suggéré par le service /// (AAAAMM_Trajets_MATRICULE_PLAQUE.pdf pour le mensuel, AAAA_Trajets_MATRICULE_PLAQUE.pdf /// pour l'annuel — les segments Matricule/Plaque sont omis s'ils sont vides). /// /// Le filtre par véhicule est optionnel : si vehiculeId est omis, le Véhicule_Courant /// est utilisé par défaut. /// /// /// Note (Exigence 7.9) : une réponse HTTP unique ne peut transporter qu'un seul fichier. /// Ces endpoints produisent donc le rapport du véhicule sélectionné (ou courant). La génération /// d'un rapport distinct par véhicule lorsqu'une période comporte plusieurs véhicules est assurée /// par les variantes de service GenererRapports…ParVehiculeAsync, réservées à un usage /// multi-fichiers ultérieur (p. ex. archive). Ici, un appel = un fichier filtré par véhicule. /// /// /// /// Erreurs : un dépassement du délai de génération ou un échec de rendu lève une /// mappée vers 503 Service Unavailable ; un véhicule /// introuvable lève une mappée vers 404 Not Found /// (réponses ProblemDetails). /// /// [ApiController] [Route("api/exports")] public class ExportsPdfController : ControllerBase { private readonly IPdfService _pdfService; private readonly IVehiculeService _vehiculeService; public ExportsPdfController(IPdfService pdfService, IVehiculeService vehiculeService) { _pdfService = pdfService; _vehiculeService = vehiculeService; } /// /// Génère et télécharge le rapport PDF mensuel d'un (année, mois) pour le véhicule indiqué /// (Véhicule_Courant si est omis). Retourne les octets du PDF /// avec Content-Type: application/pdf et Content-Disposition: attachment; /// filename="AAAAMM_Trajets_MATRICULE_PLAQUE.pdf" (Exigences 7.3, 7.4). /// [HttpGet("mensuel/{annee:int}/{mois:int}")] [Produces(MediaTypeNames.Application.Pdf)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] public async Task ExporterMensuel( int annee, int mois, [FromQuery] int? vehiculeId, CancellationToken cancellationToken) { try { var vehicule = await ResoudreVehiculeIdAsync(vehiculeId, cancellationToken); var pdf = await _pdfService.GenererRapportMensuelAsync(annee, mois, vehicule, cancellationToken); return FichierPdf(pdf); } catch (VehiculeNotFoundException ex) { return Problem(detail: ex.Message, statusCode: StatusCodes.Status404NotFound); } catch (PdfGenerationException ex) { return Problem(detail: ex.Message, statusCode: StatusCodes.Status503ServiceUnavailable); } } /// /// Génère et télécharge la synthèse PDF annuelle d'une année pour le véhicule indiqué /// (Véhicule_Courant si est omis). Retourne les octets du PDF /// avec Content-Type: application/pdf et Content-Disposition: attachment; /// filename="AAAA_Trajets_MATRICULE_PLAQUE.pdf" (Exigences 7.3, 7.4). /// [HttpGet("annuel/{annee:int}")] [Produces(MediaTypeNames.Application.Pdf)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] public async Task ExporterAnnuel( int annee, [FromQuery] int? vehiculeId, CancellationToken cancellationToken) { try { var vehicule = await ResoudreVehiculeIdAsync(vehiculeId, cancellationToken); var pdf = await _pdfService.GenererRapportAnnuelAsync(annee, vehicule, cancellationToken); return FichierPdf(pdf); } catch (VehiculeNotFoundException ex) { return Problem(detail: ex.Message, statusCode: StatusCodes.Status404NotFound); } catch (PdfGenerationException ex) { return Problem(detail: ex.Message, statusCode: StatusCodes.Status503ServiceUnavailable); } } /// /// Résout l'identifiant du véhicule à utiliser : celui fourni s'il est présent, sinon /// le Véhicule_Courant (Date_Effet la plus récente <= aujourd'hui). /// private async Task ResoudreVehiculeIdAsync(int? vehiculeId, CancellationToken cancellationToken) { if (vehiculeId.HasValue) { return vehiculeId.Value; } var courant = await _vehiculeService.GetVehiculeCourantAsync(cancellationToken); return courant.Id; } /// /// Renvoie le PDF en pièce jointe téléchargeable, en s'appuyant sur le nom de fichier /// suggéré par le (Content-Disposition: attachment). /// private FileContentResult FichierPdf(PdfResult pdf) => File(pdf.Contenu, MediaTypeNames.Application.Pdf, pdf.NomFichier); }