mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
131 lines
4.7 KiB
C#
131 lines
4.7 KiB
C#
using Data.SeaHavenIndustries.Enums;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
using SeaHaven.Services.DTOs;
|
|
|
|
namespace SeaHaven.Services.Helpers;
|
|
|
|
public static class DashboardMetrics
|
|
{
|
|
public static DashboardMetricResult Calculate(
|
|
IReadOnlyList<DashboardWorkOrder> workOrders,
|
|
DateOnly today)
|
|
{
|
|
var breakdown = new DashboardBreakdownDTO();
|
|
var dueCount = 0;
|
|
var completedDueCount = 0;
|
|
var resolutionDays = new List<decimal>();
|
|
|
|
foreach (var workOrder in workOrders)
|
|
{
|
|
var status = workOrder.LifecycleStatus
|
|
?? LifecycleStatusMapper.ParseLifecycleStatus(workOrder.LegacyStatus);
|
|
var completed = status == LifecycleStatus.Completed;
|
|
var canceled = status == LifecycleStatus.Canceled;
|
|
var pastDue = IsPastDue(workOrder, status, today);
|
|
|
|
AddBreakdownCount(breakdown, workOrder.WorkOrderType, pastDue);
|
|
|
|
var due = completed || (workOrder.ScheduledDate is DateTime scheduled
|
|
&& DateOnly.FromDateTime(scheduled.Date) <= today);
|
|
if (due)
|
|
{
|
|
dueCount++;
|
|
if (completed)
|
|
completedDueCount++;
|
|
}
|
|
|
|
if (completed && workOrder.CompletedDate is DateTime completedDate)
|
|
{
|
|
var anchor = workOrder.OriginalDate
|
|
?? (workOrder.ScheduledDate is DateTime scheduledDate
|
|
? DateOnly.FromDateTime(scheduledDate.Date)
|
|
: null);
|
|
if (anchor is DateOnly originalDate)
|
|
resolutionDays.Add((decimal)(completedDate.Date - originalDate.ToDateTime(TimeOnly.MinValue)).TotalDays);
|
|
}
|
|
}
|
|
|
|
return new DashboardMetricResult(
|
|
workOrders.Count,
|
|
workOrders.Count(workOrder =>
|
|
{
|
|
var status = workOrder.LifecycleStatus
|
|
?? LifecycleStatusMapper.ParseLifecycleStatus(workOrder.LegacyStatus);
|
|
return status is not LifecycleStatus.Completed and not LifecycleStatus.Canceled;
|
|
}),
|
|
workOrders.Count(workOrder => string.IsNullOrWhiteSpace(workOrder.AssignTo)),
|
|
workOrders.Count(workOrder =>
|
|
(workOrder.LifecycleStatus ?? LifecycleStatusMapper.ParseLifecycleStatus(workOrder.LegacyStatus))
|
|
== LifecycleStatus.Completed),
|
|
breakdown,
|
|
dueCount,
|
|
completedDueCount,
|
|
dueCount == 0 ? 0 : Math.Round((decimal)completedDueCount / dueCount * 100, 2),
|
|
resolutionDays.Count == 0 ? null : Math.Round(resolutionDays.Average(), 2),
|
|
BuildStatusDistribution(workOrders));
|
|
}
|
|
|
|
private static IReadOnlyList<DashboardStatusBucketDTO> BuildStatusDistribution(
|
|
IReadOnlyList<DashboardWorkOrder> workOrders)
|
|
=> workOrders
|
|
.GroupBy(workOrder => workOrder.LifecycleStatus
|
|
?? LifecycleStatusMapper.ParseLifecycleStatus(workOrder.LegacyStatus))
|
|
.Select(group => new DashboardStatusBucketDTO
|
|
{
|
|
Status = group.Key.HasValue
|
|
? LifecycleStatusMapper.ToFeLabel(group.Key.Value)
|
|
: "Unknown",
|
|
Count = group.Count()
|
|
})
|
|
.OrderByDescending(bucket => bucket.Count)
|
|
.ThenBy(bucket => bucket.Status, StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
|
|
private static bool IsPastDue(
|
|
DashboardWorkOrder workOrder,
|
|
LifecycleStatus? status,
|
|
DateOnly today)
|
|
=> workOrder.ScheduledDate is DateTime scheduled
|
|
&& DateOnly.FromDateTime(scheduled.Date) < today
|
|
&& status is not LifecycleStatus.Completed and not LifecycleStatus.Canceled;
|
|
|
|
private static void AddBreakdownCount(
|
|
DashboardBreakdownDTO breakdown,
|
|
WorkOrderType? type,
|
|
bool pastDue)
|
|
{
|
|
if (pastDue)
|
|
{
|
|
breakdown.Overdue++;
|
|
return;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case WorkOrderType.PM:
|
|
breakdown.PM++;
|
|
break;
|
|
case WorkOrderType.Emergency:
|
|
breakdown.Emergency++;
|
|
break;
|
|
case WorkOrderType.Reactive:
|
|
breakdown.Reactive++;
|
|
break;
|
|
default:
|
|
breakdown.Other++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed record DashboardMetricResult(
|
|
int Total,
|
|
int Open,
|
|
int NotDispatched,
|
|
int Completed,
|
|
DashboardBreakdownDTO Breakdown,
|
|
int DueCount,
|
|
int CompletedDueCount,
|
|
decimal CompletionRate,
|
|
decimal? AverageResolutionDays,
|
|
IReadOnlyList<DashboardStatusBucketDTO> StatusDistribution);
|