mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 14:13:12 +00:00
The parity test now also asserts which rows the drill-down lists: legacy open rows (status in Status or in LegacyStatus, or none) are listed and legacy closed, cancelled or assigned rows are not. Drops ticket keys from comments.
268 lines
13 KiB
C#
268 lines
13 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Helpers;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
|
|
namespace SeaHaven.DataServices.Implementation
|
|
{
|
|
public class DashboardDataService : IDashboardDataService
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
|
|
public DashboardDataService(ApplicationDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<DashboardCounts> GetWorkOrderCountsAsync(
|
|
int? accountId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var query = _context.workOrders.Where(w => w.istemplate != true);
|
|
if (accountId is int scopedAccountId)
|
|
{
|
|
query = query.Where(w => w.AccountId == scopedAccountId);
|
|
}
|
|
|
|
var total = await query.CountAsync(cancellationToken);
|
|
var open = await query.CountAsync(w => w.Status == "Open" || w.Status == "In Progress" || w.Status == "On Hold", cancellationToken);
|
|
var notDispatched = await query.CountAsync(w => (w.Status == "Open") && (w.AssignTo == null || w.AssignTo == ""), cancellationToken);
|
|
var completed = await query.CountAsync(w => w.Status == "Done", cancellationToken);
|
|
|
|
return new DashboardCounts
|
|
{
|
|
Total = total,
|
|
Open = open,
|
|
NotDispatched = notDispatched,
|
|
Completed = completed
|
|
};
|
|
}
|
|
|
|
private static readonly IReadOnlyList<LifecycleStatus> OpenLifecycleStatuses = Enum
|
|
.GetValues<LifecycleStatus>()
|
|
.Where(s => s != LifecycleStatus.Completed && s != LifecycleStatus.Canceled)
|
|
.ToList();
|
|
|
|
public async Task<int> CountOpenUnassignedAsync(
|
|
int? accountId,
|
|
string? dispatcherId,
|
|
DateOnly? dateFrom,
|
|
DateOnly? dateTo,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// A dispatcher-scoped Dashboard has no unassigned work by definition.
|
|
if (dispatcherId != null)
|
|
return 0;
|
|
|
|
var query = WorkOrderBoardQueryFilters.ApplyBaseScope(_context.workOrders.AsNoTracking());
|
|
if (accountId is int scopedAccountId)
|
|
query = WorkOrderBoardQueryFilters.ApplyAccountScope(query, scopedAccountId);
|
|
query = WorkOrderBoardQueryFilters.ApplyDispatcherFilter(
|
|
query, new[] { "__unassigned__" }, myWorkOrders: false, currentUserId: null);
|
|
query = WorkOrderBoardQueryFilters.ApplyStatusFilter(query, OpenLifecycleStatuses);
|
|
// All time counts what the board lists with no range: every week plus
|
|
// undated work, so the tile and its drill-down agree.
|
|
query = dateFrom is DateOnly from && dateTo is DateOnly to
|
|
? WorkOrderBoardQueryFilters.ApplyDateRangeFilter(query, from, to)
|
|
: WorkOrderBoardQueryFilters.ApplyDateRangeFilter(
|
|
query,
|
|
WorkOrderBoardQueryFilters.AllWeeksFrom,
|
|
WorkOrderBoardQueryFilters.AllWeeksTo,
|
|
includeDateless: true);
|
|
|
|
return await query.CountAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<DashboardWorkOrder>> GetDashboardWorkOrdersAsync(
|
|
int? accountId,
|
|
string? dispatcherId,
|
|
DateOnly? dateFrom,
|
|
DateOnly? dateTo,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var query = WorkOrderBoardQueryFilters.ApplyBaseScope(_context.workOrders.AsNoTracking());
|
|
if (accountId is int scopedAccountId)
|
|
query = WorkOrderBoardQueryFilters.ApplyAccountScope(query, scopedAccountId);
|
|
|
|
if (dispatcherId != null)
|
|
query = query.Where(w => w.AssignTo == dispatcherId);
|
|
|
|
if (dateFrom is DateOnly from && dateTo is DateOnly to)
|
|
{
|
|
var fromDate = from.ToDateTime(TimeOnly.MinValue);
|
|
var toDateExclusive = to.AddDays(1).ToDateTime(TimeOnly.MinValue);
|
|
query = query.Where(w =>
|
|
w.ScheduledDate >= fromDate && w.ScheduledDate < toDateExclusive);
|
|
}
|
|
|
|
return await query
|
|
.Select(w => new DashboardWorkOrder(
|
|
w.Id,
|
|
w.AssignTo,
|
|
w.WorkOrderType,
|
|
w.LifecycleStatus,
|
|
w.LegacyStatus ?? w.Status,
|
|
w.ScheduledDate,
|
|
w.OriginalDate,
|
|
w.CompletedDate,
|
|
w.Locations == null ? null : w.Locations.State,
|
|
w.RescheduleCount,
|
|
w.PrimaryDispatch == null || w.PrimaryDispatch.Vendor == null
|
|
? null
|
|
: w.PrimaryDispatch.Vendor.CompanyId,
|
|
w.PrimaryDispatch == null || w.PrimaryDispatch.Vendor == null
|
|
|| w.PrimaryDispatch.Vendor.Company == null
|
|
? null
|
|
: w.PrimaryDispatch.Vendor.Company.Name,
|
|
w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
|
&& w.PrimaryDispatch.Vendor.IsActive,
|
|
w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
|
&& w.PrimaryDispatch.Vendor.Company != null
|
|
&& w.PrimaryDispatch.Vendor.Company.IsDeleted == true))
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<DashboardKpiCounts> GetKpiCountsAsync(
|
|
int? accountId,
|
|
string? dispatcherId,
|
|
DateOnly today,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var tomorrow = today.AddDays(1);
|
|
var dayAfterTomorrow = tomorrow.AddDays(1);
|
|
var workOrders = WorkOrderBoardQueryFilters.ApplyBaseScope(_context.workOrders.AsNoTracking());
|
|
if (accountId is int scopedAccountId)
|
|
workOrders = WorkOrderBoardQueryFilters.ApplyAccountScope(workOrders, scopedAccountId);
|
|
if (dispatcherId != null)
|
|
workOrders = workOrders.Where(w => w.AssignTo == dispatcherId);
|
|
|
|
var scheduledTomorrow = await workOrders
|
|
.Where(w => w.ScheduledDate >= tomorrow.ToDateTime(TimeOnly.MinValue)
|
|
&& w.ScheduledDate < dayAfterTomorrow.ToDateTime(TimeOnly.MinValue))
|
|
.CountAsync(cancellationToken);
|
|
|
|
var avetaPending = await workOrders
|
|
.Where(w => w.ScheduledDate >= today.ToDateTime(TimeOnly.MinValue)
|
|
&& w.ScheduledDate < dayAfterTomorrow.ToDateTime(TimeOnly.MinValue)
|
|
&& w.AvetaRequired
|
|
&& w.LifecycleStatus != LifecycleStatus.Completed
|
|
&& w.LifecycleStatus != LifecycleStatus.Canceled
|
|
&& (w.LifecycleStatus != null
|
|
|| ((w.LegacyStatus ?? w.Status) != "Done"
|
|
&& (w.LegacyStatus ?? w.Status) != "Completed"
|
|
&& (w.LegacyStatus ?? w.Status) != "Complete"
|
|
&& (w.LegacyStatus ?? w.Status) != "Closed"
|
|
&& (w.LegacyStatus ?? w.Status) != "Canceled"
|
|
&& (w.LegacyStatus ?? w.Status) != "Cancelled"))
|
|
&& !w.workOrderAttachments!.Any(attachment =>
|
|
(attachment.IsDeleted == null || attachment.IsDeleted == false)
|
|
&& attachment.Category == WorkOrderMediaCategory.Aveta))
|
|
.CountAsync(cancellationToken);
|
|
|
|
var pendingUplifts = _context.DispatchUpliftRequests
|
|
.AsNoTracking()
|
|
.Where(request => request.Status == "Pending"
|
|
&& (request.IsDeleted == null || request.IsDeleted == false)
|
|
&& request.Dispatch != null
|
|
&& (request.Dispatch.IsDeleted == null || request.Dispatch.IsDeleted == false));
|
|
if (accountId is int account)
|
|
{
|
|
pendingUplifts = pendingUplifts.Where(request =>
|
|
(request.Dispatch!.WorkOrder != null && request.Dispatch.WorkOrder.AccountId == account)
|
|
|| request.Dispatch.DispatchWorkOrders!.Any(link =>
|
|
link.WorkOrder != null && link.WorkOrder.AccountId == account));
|
|
}
|
|
if (dispatcherId != null)
|
|
{
|
|
pendingUplifts = pendingUplifts.Where(request =>
|
|
(request.Dispatch!.WorkOrder != null && request.Dispatch.WorkOrder.AssignTo == dispatcherId)
|
|
|| request.Dispatch.DispatchWorkOrders!.Any(link =>
|
|
link.WorkOrder != null && link.WorkOrder.AssignTo == dispatcherId));
|
|
}
|
|
|
|
return new DashboardKpiCounts(
|
|
scheduledTomorrow,
|
|
await pendingUplifts.CountAsync(cancellationToken),
|
|
avetaPending);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<DashboardDispatcherWorkload>> GetDispatcherWorkloadAsync(
|
|
int? accountId,
|
|
string? dispatcherId,
|
|
DateOnly? dateFrom,
|
|
DateOnly? dateTo,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var dispatcherUsers = from user in _context.Users.AsNoTracking()
|
|
join userRole in _context.UserRoles
|
|
on user.Id equals userRole.UserId
|
|
join role in _context.Roles
|
|
on userRole.RoleId equals role.Id
|
|
where user.IsDeleted != true
|
|
&& (role.Name == "Dispatcher" || role.Name == "DISPATCHER")
|
|
select new
|
|
{
|
|
user.Id,
|
|
Name = ((user.FirstName ?? "") + " " + (user.LastName ?? "")).Trim(),
|
|
user.Color
|
|
};
|
|
|
|
var workOrders = WorkOrderBoardQueryFilters.ApplyBaseScope(_context.workOrders.AsNoTracking());
|
|
if (accountId is int scopedAccountId)
|
|
workOrders = WorkOrderBoardQueryFilters.ApplyAccountScope(workOrders, scopedAccountId);
|
|
|
|
if (dispatcherId != null)
|
|
workOrders = workOrders.Where(w => w.AssignTo == dispatcherId);
|
|
|
|
if (dateFrom is DateOnly from && dateTo is DateOnly to)
|
|
{
|
|
var fromDate = from.ToDateTime(TimeOnly.MinValue);
|
|
var toDateExclusive = to.AddDays(1).ToDateTime(TimeOnly.MinValue);
|
|
workOrders = workOrders.Where(w =>
|
|
w.ScheduledDate >= fromDate && w.ScheduledDate < toDateExclusive);
|
|
}
|
|
|
|
var counts = await workOrders
|
|
.Where(w => w.AssignTo != null)
|
|
.GroupBy(w => w.AssignTo!)
|
|
.Select(group => new
|
|
{
|
|
DispatcherId = group.Key,
|
|
TotalCount = group.Count(),
|
|
OpenCount = group.Count(w =>
|
|
w.LifecycleStatus != LifecycleStatus.Completed
|
|
&& w.LifecycleStatus != LifecycleStatus.Canceled
|
|
&& (w.LifecycleStatus != null
|
|
|| (w.LegacyStatus ?? w.Status) != "Done"
|
|
&& (w.LegacyStatus ?? w.Status) != "Completed"
|
|
&& (w.LegacyStatus ?? w.Status) != "Complete"
|
|
&& (w.LegacyStatus ?? w.Status) != "Closed"
|
|
&& (w.LegacyStatus ?? w.Status) != "Canceled"
|
|
&& (w.LegacyStatus ?? w.Status) != "Cancelled"))
|
|
})
|
|
.ToListAsync(cancellationToken);
|
|
|
|
var dispatcherList = await dispatcherUsers
|
|
.Distinct()
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return dispatcherList
|
|
.GroupJoin(
|
|
counts,
|
|
user => user.Id,
|
|
count => count.DispatcherId,
|
|
(user, matchingCounts) =>
|
|
{
|
|
var count = matchingCounts.SingleOrDefault();
|
|
return new DashboardDispatcherWorkload(
|
|
user.Id,
|
|
user.Name,
|
|
user.Color,
|
|
count?.TotalCount ?? 0,
|
|
count?.OpenCount ?? 0);
|
|
})
|
|
.ToList();
|
|
}
|
|
}
|
|
}
|