mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
feat(dashboard): add dispatcher performance (SH-347)
This commit is contained in:
parent
9b39d0e5ec
commit
0194748e3b
7 changed files with 172 additions and 5 deletions
|
|
@ -237,5 +237,68 @@ public class DashboardServiceTests
|
|||
adminResult.TotalDispatchers.Should().Be(2);
|
||||
adminResult.Items.Select(item => item.DispatcherName)
|
||||
.Should().ContainInOrder("Ada One", "Ben Two");
|
||||
|
||||
var performance = await NewService(ctx).GetPerformanceAsync(
|
||||
AccountUser(1),
|
||||
new DashboardStatsQueryDTO(),
|
||||
1,
|
||||
CancellationToken.None);
|
||||
|
||||
performance.TotalDispatchers.Should().Be(2);
|
||||
performance.Items[0].DispatcherName.Should().Be("Ada One");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPerformanceAsync_UsesDueOnlyRateAndOriginalDateResolution()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
ctx.Roles.Add(new IdentityRole
|
||||
{
|
||||
Id = "dispatcher-role",
|
||||
Name = "Dispatcher",
|
||||
NormalizedName = "DISPATCHER"
|
||||
});
|
||||
ctx.Users.Add(new ApplicationUser
|
||||
{
|
||||
Id = "dispatcher-1",
|
||||
FirstName = "Ada",
|
||||
LastName = "One",
|
||||
Color = "#123456"
|
||||
});
|
||||
ctx.UserRoles.Add(new IdentityUserRole<string>
|
||||
{
|
||||
UserId = "dispatcher-1",
|
||||
RoleId = "dispatcher-role"
|
||||
});
|
||||
var today = DashboardBusinessTime.Today();
|
||||
ctx.workOrders.AddRange(
|
||||
new WorkOrder
|
||||
{
|
||||
AccountId = 1,
|
||||
AssignTo = "dispatcher-1",
|
||||
LifecycleStatus = Data.SeaHavenIndustries.Enums.LifecycleStatus.Completed,
|
||||
ScheduledDate = today.ToDateTime(TimeOnly.MinValue),
|
||||
OriginalDate = today.AddDays(-5),
|
||||
CompletedDate = today.AddDays(-2).ToDateTime(TimeOnly.MinValue)
|
||||
},
|
||||
new WorkOrder
|
||||
{
|
||||
AccountId = 1,
|
||||
AssignTo = "dispatcher-1",
|
||||
LifecycleStatus = Data.SeaHavenIndustries.Enums.LifecycleStatus.Scheduled,
|
||||
ScheduledDate = today.AddDays(3).ToDateTime(TimeOnly.MinValue)
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
|
||||
var result = await NewService(ctx).GetPerformanceAsync(
|
||||
AccountUser(1),
|
||||
new DashboardStatsQueryDTO(),
|
||||
1,
|
||||
CancellationToken.None);
|
||||
|
||||
result.Items.Should().ContainSingle();
|
||||
result.Items[0].CompletionRate.Should().Be(100m);
|
||||
result.Items[0].AverageResolutionDays.Should().Be(3m);
|
||||
result.Items[0].Color.Should().Be("#123456");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,5 +63,31 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("Performance")]
|
||||
public async Task<IActionResult> GetPerformance(
|
||||
[FromQuery] DashboardStatsQueryDTO query,
|
||||
[FromQuery] int page = 1,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dashboardService.GetPerformanceAsync(
|
||||
User, query, page, cancellationToken));
|
||||
}
|
||||
catch (SeaHaven.Services.Exceptions.WorkOrderBoardValidationException ex)
|
||||
when (ex.Code == "Forbidden")
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new
|
||||
{
|
||||
code = ex.Code,
|
||||
message = ex.Message
|
||||
});
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ namespace SeaHaven.DataServices.Implementation
|
|||
select new
|
||||
{
|
||||
user.Id,
|
||||
Name = ((user.FirstName ?? "") + " " + (user.LastName ?? "")).Trim()
|
||||
Name = ((user.FirstName ?? "") + " " + (user.LastName ?? "")).Trim(),
|
||||
user.Color
|
||||
};
|
||||
|
||||
var workOrders = WorkOrderBoardQueryFilters.ApplyBaseScope(_context.workOrders.AsNoTracking());
|
||||
|
|
@ -140,10 +141,11 @@ namespace SeaHaven.DataServices.Implementation
|
|||
{
|
||||
var count = matchingCounts.SingleOrDefault();
|
||||
return new DashboardDispatcherWorkload(
|
||||
user.Id,
|
||||
user.Name,
|
||||
count?.TotalCount ?? 0,
|
||||
count?.OpenCount ?? 0);
|
||||
user.Id,
|
||||
user.Name,
|
||||
user.Color,
|
||||
count?.TotalCount ?? 0,
|
||||
count?.OpenCount ?? 0);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ namespace SeaHaven.DataServices.Interfaces
|
|||
public sealed record DashboardDispatcherWorkload(
|
||||
string DispatcherId,
|
||||
string DispatcherName,
|
||||
string? Color,
|
||||
int TotalCount,
|
||||
int OpenCount);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,28 @@ namespace SeaHaven.Services.DTOs
|
|||
{
|
||||
public string DispatcherId { get; init; } = string.Empty;
|
||||
public string DispatcherName { get; init; } = string.Empty;
|
||||
public string? Color { get; init; }
|
||||
public int TotalCount { get; init; }
|
||||
public int OpenCount { get; init; }
|
||||
}
|
||||
|
||||
public sealed class DashboardPerformanceResponseDTO
|
||||
{
|
||||
public IReadOnlyList<DashboardPerformanceRowDTO> Items { get; init; } = [];
|
||||
public int Page { get; init; }
|
||||
public int PageSize { get; init; }
|
||||
public int TotalDispatchers { get; init; }
|
||||
}
|
||||
|
||||
public sealed class DashboardPerformanceRowDTO
|
||||
{
|
||||
public string DispatcherId { get; init; } = string.Empty;
|
||||
public string DispatcherName { get; init; } = string.Empty;
|
||||
public string? Color { get; init; }
|
||||
public decimal CompletionRate { get; init; }
|
||||
public decimal? AverageResolutionDays { get; init; }
|
||||
}
|
||||
|
||||
public class DashboardStatsDTO
|
||||
{
|
||||
public int Total { get; set; }
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ namespace SeaHaven.Services.Implementation
|
|||
{
|
||||
DispatcherId = row.DispatcherId,
|
||||
DispatcherName = row.DispatcherName,
|
||||
Color = row.Color,
|
||||
TotalCount = row.TotalCount,
|
||||
OpenCount = row.OpenCount
|
||||
}).ToList(),
|
||||
|
|
@ -101,5 +102,55 @@ namespace SeaHaven.Services.Implementation
|
|||
TotalDispatchers = orderedRows.Count
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<DashboardPerformanceResponseDTO> GetPerformanceAsync(
|
||||
ClaimsPrincipal user,
|
||||
DashboardStatsQueryDTO query,
|
||||
int page,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (page < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
|
||||
var accountId = _accountResolver.ResolveAccountFilter(user);
|
||||
var dispatcherId = user.IsInRole("Dispatcher")
|
||||
? user.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
: null;
|
||||
if (user.IsInRole("Dispatcher") && string.IsNullOrWhiteSpace(dispatcherId))
|
||||
throw new WorkOrderBoardValidationException("Forbidden", "Dispatcher identity is required.");
|
||||
|
||||
var directory = await _dataService.GetDispatcherWorkloadAsync(
|
||||
accountId, dispatcherId, query.DateFrom, query.DateTo, cancellationToken);
|
||||
var workOrders = await _dataService.GetDashboardWorkOrdersAsync(
|
||||
accountId, dispatcherId, query.DateFrom, query.DateTo, cancellationToken);
|
||||
var today = DashboardBusinessTime.Today();
|
||||
var rows = directory.Select(dispatcher =>
|
||||
{
|
||||
var metrics = DashboardMetrics.Calculate(
|
||||
workOrders.Where(workOrder => workOrder.AssignTo == dispatcher.DispatcherId).ToList(),
|
||||
today);
|
||||
return new DashboardPerformanceRowDTO
|
||||
{
|
||||
DispatcherId = dispatcher.DispatcherId,
|
||||
DispatcherName = dispatcher.DispatcherName,
|
||||
Color = dispatcher.Color,
|
||||
CompletionRate = metrics.CompletionRate,
|
||||
AverageResolutionDays = metrics.AverageResolutionDays
|
||||
};
|
||||
})
|
||||
.OrderByDescending(row => row.CompletionRate)
|
||||
.ThenBy(row => row.DispatcherName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(row => row.DispatcherId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
const int pageSize = 10;
|
||||
|
||||
return new DashboardPerformanceResponseDTO
|
||||
{
|
||||
Items = rows.Skip((page - 1) * pageSize).Take(pageSize).ToList(),
|
||||
Page = page,
|
||||
PageSize = pageSize,
|
||||
TotalDispatchers = rows.Count
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,5 +15,11 @@ namespace SeaHaven.Services.Interfaces
|
|||
DashboardStatsQueryDTO query,
|
||||
int page,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<DashboardPerformanceResponseDTO> GetPerformanceAsync(
|
||||
ClaimsPrincipal user,
|
||||
DashboardStatsQueryDTO query,
|
||||
int page,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue