Application complète de saisie et de suivi des trajets kilométriques avec calcul de distance routière (OpenRouteService), barème kilométrique, résumés et régularisation annuelle. Backend (ASP.NET Core .NET 8, EF Core, SQLite) : - Trajets (CRUD, réordonnancement, insertion), calcul de distance ORS avec fallback Haversine, géométrie d'itinéraire stockée par trajet, recalcul en masse. - Profil, véhicules (avec dénomination), carnet d'adresses nommées, cache d'adresses. - Barème kilométrique : tranches multiples par puissance fiscale, borne max exclusive avec 'illimité' (KmMax nullable), coefficient à 3 décimales, recopie du dernier barème connu. - Résumés mensuel/annuel, montants réglés, régularisation, export PDF. Frontend (Angular 22, zoneless + signals) : - Consultation par jour, navigation temporelle synchronisée, carte Leaflet, formulaire de trajet (adresses nommées, domicile, autocomplétion BAN). - Détection de changement zoneless (indépendante de zone.js). Hébergement : Docker Compose (nginx + backend), scripts run-dev.sh / run-docker.sh.
127 lines
5.5 KiB
C#
127 lines
5.5 KiB
C#
using System.Net.Mime;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using TrajetKmTracker.Api.Services;
|
|
using TrajetKmTracker.Api.Services.Exceptions;
|
|
|
|
namespace TrajetKmTracker.Api.Controllers;
|
|
|
|
/// <summary>
|
|
/// Endpoints d'Export_PDF : rapport mensuel et synthèse annuelle (Exigences 7.3, 7.4, 7.5).
|
|
/// Chaque réponse renvoie un unique fichier <c>application/pdf</c> avec l'en-tête
|
|
/// <c>Content-Disposition: attachment</c> et le nom de fichier suggéré par le service
|
|
/// (<c>trajets_MM_AAAA.pdf</c> pour le mensuel, <c>trajets_AAAA.pdf</c> pour l'annuel).
|
|
///
|
|
/// Le filtre par véhicule est optionnel : si <c>vehiculeId</c> est omis, le Véhicule_Courant
|
|
/// est utilisé par défaut.
|
|
///
|
|
/// <para>
|
|
/// 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 <c>GenererRapports…ParVehiculeAsync</c>, réservées à un usage
|
|
/// multi-fichiers ultérieur (p. ex. archive). Ici, un appel = un fichier filtré par véhicule.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Erreurs : un dépassement du délai de génération ou un échec de rendu lève une
|
|
/// <see cref="PdfGenerationException"/> mappée vers <c>503 Service Unavailable</c> ; un véhicule
|
|
/// introuvable lève une <see cref="VehiculeNotFoundException"/> mappée vers <c>404 Not Found</c>
|
|
/// (réponses ProblemDetails).
|
|
/// </para>
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <paramref name="vehiculeId"/> est omis). Retourne les octets du PDF
|
|
/// avec <c>Content-Type: application/pdf</c> et <c>Content-Disposition: attachment;
|
|
/// filename="trajets_MM_AAAA.pdf"</c> (Exigences 7.3, 7.4).
|
|
/// </summary>
|
|
[HttpGet("mensuel/{annee:int}/{mois:int}")]
|
|
[Produces(MediaTypeNames.Application.Pdf)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
|
public async Task<IActionResult> 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <paramref name="vehiculeId"/> est omis). Retourne les octets du PDF
|
|
/// avec <c>Content-Type: application/pdf</c> et <c>Content-Disposition: attachment;
|
|
/// filename="trajets_AAAA.pdf"</c> (Exigences 7.3, 7.4).
|
|
/// </summary>
|
|
[HttpGet("annuel/{annee:int}")]
|
|
[Produces(MediaTypeNames.Application.Pdf)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
|
public async Task<IActionResult> 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
private async Task<int> ResoudreVehiculeIdAsync(int? vehiculeId, CancellationToken cancellationToken)
|
|
{
|
|
if (vehiculeId.HasValue)
|
|
{
|
|
return vehiculeId.Value;
|
|
}
|
|
|
|
var courant = await _vehiculeService.GetVehiculeCourantAsync(cancellationToken);
|
|
return courant.Id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renvoie le PDF en pièce jointe téléchargeable, en s'appuyant sur le nom de fichier
|
|
/// suggéré par le <see cref="PdfResult"/> (<c>Content-Disposition: attachment</c>).
|
|
/// </summary>
|
|
private FileContentResult FichierPdf(PdfResult pdf)
|
|
=> File(pdf.Contenu, MediaTypeNames.Application.Pdf, pdf.NomFichier);
|
|
}
|