diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 2449078..9a8943e 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -1,22 +1,39 @@ using System.Diagnostics; using AVSCartonShipmentVerifier.Models; +using AVSCartonShipmentVerifier.Repositories; using AVSCartonShipmentVerifier.Services; using AVSCartonShipmentVerifier.ViewModels; using Microsoft.AspNetCore.Mvc; namespace AVSCartonShipmentVerifier.Controllers; -public sealed class HomeController(IScanHistoryStore scanHistoryStore) : Controller +public sealed class HomeController( + IScanHistoryStore scanHistoryStore, + ICartonVerificationRepository cartonVerificationRepository, + ILogger logger) : Controller { private readonly IScanHistoryStore _scanHistoryStore = scanHistoryStore; + private readonly ICartonVerificationRepository _cartonVerificationRepository = cartonVerificationRepository; + private readonly ILogger _logger = logger; [HttpGet] public async Task Index(CancellationToken cancellationToken) { + int? availableCartonsCount = null; + try + { + availableCartonsCount = await _cartonVerificationRepository.GetAvailableCartonCountAsync(cancellationToken); + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Failed to load available cartons count for dashboard."); + } + var model = new VerificationDashboardViewModel { RecentSuccessfulScans = await _scanHistoryStore.GetLastFiveSuccessfulScansAsync(cancellationToken), - RecentRejectedScans = await _scanHistoryStore.GetLastFiveRejectedScansAsync(cancellationToken) + RecentRejectedScans = await _scanHistoryStore.GetLastFiveRejectedScansAsync(cancellationToken), + AvailableCartonsCount = availableCartonsCount }; return View(model); diff --git a/Controllers/VerificationController.cs b/Controllers/VerificationController.cs index 9a8d76b..cf77f87 100644 --- a/Controllers/VerificationController.cs +++ b/Controllers/VerificationController.cs @@ -1,4 +1,5 @@ using AVSCartonShipmentVerifier.DTOs; +using AVSCartonShipmentVerifier.Repositories; using AVSCartonShipmentVerifier.Services; using Microsoft.AspNetCore.Mvc; @@ -8,10 +9,14 @@ namespace AVSCartonShipmentVerifier.Controllers; public sealed class VerificationController( IShipmentVerificationService shipmentVerificationService, IScanHistoryStore scanHistoryStore, + ICartonVerificationRepository cartonVerificationRepository, + IMovementAnalyticsService movementAnalyticsService, ILogger logger) : Controller { private readonly IShipmentVerificationService _shipmentVerificationService = shipmentVerificationService; private readonly IScanHistoryStore _scanHistoryStore = scanHistoryStore; + private readonly ICartonVerificationRepository _cartonVerificationRepository = cartonVerificationRepository; + private readonly IMovementAnalyticsService _movementAnalyticsService = movementAnalyticsService; private readonly ILogger _logger = logger; [HttpPost("scan")] @@ -30,11 +35,14 @@ public sealed class VerificationController( request.EntryMethod ?? "Scanner", cancellationToken); + var availableCartons = await TryGetAvailableCartonCountAsync(cancellationToken); + return Ok(new { result = verificationResult, recentSuccessfulScans = await _scanHistoryStore.GetLastFiveSuccessfulScansAsync(cancellationToken), - recentRejectedScans = await _scanHistoryStore.GetLastFiveRejectedScansAsync(cancellationToken) + recentRejectedScans = await _scanHistoryStore.GetLastFiveRejectedScansAsync(cancellationToken), + availableCartons }); } catch (OperationCanceledException) @@ -52,6 +60,52 @@ public sealed class VerificationController( } } + [HttpGet("dashboard-summary")] + public async Task GetDashboardSummary(CancellationToken cancellationToken) + { + var count = await TryGetAvailableCartonCountAsync(cancellationToken); + if (count is null) + { + return StatusCode(StatusCodes.Status503ServiceUnavailable, new { message = "Unable to load available cartons count." }); + } + + return Ok(new { availableCartons = count.Value }); + } + + [HttpGet("available-cartons")] + public async Task GetAvailableCartons(CancellationToken cancellationToken) + { + var count = await TryGetAvailableCartonCountAsync(cancellationToken); + if (count is null) + { + return StatusCode(StatusCodes.Status503ServiceUnavailable, new { message = "Unable to load available cartons count." }); + } + + return Ok(new { availableCartons = count.Value }); + } + + [HttpGet("movement-analytics")] + public async Task GetMovementAnalytics( + [FromQuery] DateOnly? from, + [FromQuery] DateOnly? to, + CancellationToken cancellationToken) + { + var today = DateOnly.FromDateTime(DateTime.Now); + var toDate = to ?? today; + var fromDate = from ?? toDate.AddDays(-6); + + try + { + var analytics = await _movementAnalyticsService.GetAnalyticsAsync(fromDate, toDate, cancellationToken); + return Ok(analytics); + } + catch (Exception exception) + { + _logger.LogError(exception, "Unexpected error while loading movement analytics."); + return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Unable to load movement analytics." }); + } + } + /// /// Returns all successful or all rejected scan records from SQLite (not limited to five). /// @@ -81,5 +135,18 @@ public sealed class VerificationController( return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Unable to load scan history." }); } } + + private async Task TryGetAvailableCartonCountAsync(CancellationToken cancellationToken) + { + try + { + return await _cartonVerificationRepository.GetAvailableCartonCountAsync(cancellationToken); + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Failed to load available cartons count from verification database."); + return null; + } + } } diff --git a/ViewModels/VerificationDashboardViewModel.cs b/ViewModels/VerificationDashboardViewModel.cs index dbd0f13..6cd6177 100644 --- a/ViewModels/VerificationDashboardViewModel.cs +++ b/ViewModels/VerificationDashboardViewModel.cs @@ -7,5 +7,8 @@ public sealed class VerificationDashboardViewModel public IReadOnlyCollection RecentSuccessfulScans { get; init; } = []; public IReadOnlyCollection RecentRejectedScans { get; init; } = []; + + /// Cartons with moved_to_warehouse = 0; null when MySQL count could not be loaded. + public int? AvailableCartonsCount { get; init; } }