using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Interfaces; namespace SeaHaven.DataServices.Helpers { public static class WorkOrderBoardProjection { public static async Task> ProjectRowsAsync( ApplicationDbContext context, IQueryable query, bool isUnscheduled, CancellationToken cancellationToken = default) { var rows = await query .Select(w => new { w.Id, w.InternalWONumber, w.RescheduleCount, w.CarriedOver, w.IsAddOn, w.AvetaRequired, HasAvetaDocument = w.workOrderAttachments!.Any(a => (a.IsDeleted == null || a.IsDeleted == false) && a.Category == WorkOrderMediaCategory.Aveta), MediaCount = w.workOrderAttachments!.Count(a => a.IsDeleted == null || a.IsDeleted == false), w.OriginalDate, w.OriginalWeek, w.WorkOrderType, w.SiteCode, LocationName = w.Locations != null ? w.Locations.Name : null, WoPocName = w.PocName, WoPocPhone = w.PocPhone, WoPocNotes = w.PocNotes, ContactPoc = w.WorkOrderContacts! .OrderBy(c => c.Id) .Select(c => new { Name = c.POC != null ? ((c.POC.FirstName ?? "") + " " + (c.POC.LastName ?? "")).Trim() : null, PhoneNumber = c.POC != null ? c.POC.PhoneNumber : null, c.Notes }) .FirstOrDefault(), w.LifecycleStatus, w.LegacyStatus, w.Status, w.AssignTo, w.AssignToUser!.FirstName, w.AssignToUser!.LastName, w.AssignToUser!.Initials, w.AssignToUser!.Color, w.DueDate, w.ScheduledDate, w.ScheduledStart, w.ScheduledEnd, w.TargetWeek, w.ScheduleWeekOnly, PrimaryDispatchStatus = w.PrimaryDispatch != null ? w.PrimaryDispatch.Status : null, VendorId = w.PrimaryDispatch != null ? (int?)w.PrimaryDispatch.VendorId : null, VendorName = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null ? w.PrimaryDispatch.Vendor.CompanyName : null, TechName = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null ? w.PrimaryDispatch.Vendor.ContactName : null, DispatchTechPhone = w.PrimaryDispatch != null ? w.PrimaryDispatch.TechPhone : null, WoTechPhone = w.TechPhone, VendorPhone = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null ? w.PrimaryDispatch.Vendor.Phone : null, DispatchApptDate = w.PrimaryDispatch != null ? w.PrimaryDispatch.ScheduledDate : null, w.Trade, w.Problem, w.ServiceNotes, w.ExtraServices, w.AdditionalContacts, w.DocStatus, w.CompletedDate, w.FlagColor, w.PrimaryDispatchId, w.RowVersion, DispatchRowVersion = w.PrimaryDispatch != null ? w.PrimaryDispatch.RowVersion : null, w.Severity }) .ToListAsync(cancellationToken); if (rows.Count == 0) return Array.Empty(); var upliftByWorkOrder = await LoadUpliftAggregatesAsync(context, query, cancellationToken); return rows.Select(w => { var pocName = !string.IsNullOrWhiteSpace(w.WoPocName) ? w.WoPocName : w.ContactPoc?.Name; var pocPhone = !string.IsNullOrWhiteSpace(w.WoPocPhone) ? w.WoPocPhone : w.ContactPoc?.PhoneNumber; var pocNotes = !string.IsNullOrWhiteSpace(w.WoPocNotes) ? w.WoPocNotes : w.ContactPoc?.Notes; var techPhone = !string.IsNullOrWhiteSpace(w.DispatchTechPhone) ? w.DispatchTechPhone : (!string.IsNullOrWhiteSpace(w.WoTechPhone) ? w.WoTechPhone : w.VendorPhone); var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor( w.VendorId, w.VendorName, w.PrimaryDispatchStatus); upliftByWorkOrder.TryGetValue(w.Id, out var uplift); return new WorkOrderBoardRawRow( w.Id, w.InternalWONumber, w.RescheduleCount, w.CarriedOver, w.IsAddOn, w.WorkOrderType, w.SiteCode, w.LocationName, pocName, pocPhone, pocNotes, w.LifecycleStatus, w.LegacyStatus ?? w.Status, w.AssignTo, w.FirstName, w.LastName, w.Initials, w.Color, w.DueDate, w.ScheduledDate, w.ScheduledStart, w.ScheduledEnd, w.TargetWeek, w.ScheduleWeekOnly, vendorId, vendorName, w.TechName, techPhone, w.DispatchApptDate, w.Trade ?? w.Problem, w.Problem, w.ServiceNotes, w.ExtraServices, w.AdditionalContacts, w.DocStatus, w.CompletedDate, w.FlagColor, w.PrimaryDispatchId, w.RowVersion, w.DispatchRowVersion, uplift?.PendingCount ?? 0, uplift?.HasUplift ?? false, uplift?.PrimaryStatus, uplift?.PrimaryAmount, w.PrimaryDispatchStatus, w.AvetaRequired, w.HasAvetaDocument, w.OriginalDate, w.OriginalWeek, isUnscheduled, w.Severity, w.MediaCount); }).ToList(); } /// /// SH-188: one round-trip for live uplifts on WOs in /// (primary WorkOrderId and multi-WO DispatchWorkOrders links). Uses the same /// IQueryable as a SQL subquery so large boards do not hit the 2100-parameter cap. /// private static async Task> LoadUpliftAggregatesAsync( ApplicationDbContext context, IQueryable workOrders, CancellationToken cancellationToken) { var workOrderIds = workOrders.Select(w => w.Id); var live = context.DispatchUpliftRequests .AsNoTracking() .Where(u => (u.IsDeleted == null || u.IsDeleted == false) && u.Dispatch != null && (u.Dispatch.IsDeleted == null || u.Dispatch.IsDeleted == false)); var fromPrimary = live .Where(u => u.Dispatch!.WorkOrderId != null && workOrderIds.Contains(u.Dispatch.WorkOrderId.Value)) .Select(u => new { UpliftId = u.Id, WorkOrderId = u.Dispatch!.WorkOrderId!.Value, u.Status, u.RequestedNTE, u.CreatedDate }); var fromLinks = live .SelectMany( u => u.Dispatch!.DispatchWorkOrders! .Where(link => workOrderIds.Contains(link.WorkOrderId)), (u, link) => new { UpliftId = u.Id, WorkOrderId = link.WorkOrderId, u.Status, u.RequestedNTE, u.CreatedDate }); var links = await fromPrimary.Concat(fromLinks).ToListAsync(cancellationToken); return AggregateUpliftLinks(links.Select(l => new UpliftLinkRow( l.UpliftId, l.WorkOrderId, l.Status, l.RequestedNTE, l.CreatedDate))); } private static Dictionary AggregateUpliftLinks(IEnumerable links) { var seen = new HashSet<(int UpliftId, int WorkOrderId)>(); var byWorkOrder = new Dictionary>(); foreach (var link in links) { if (!seen.Add((link.UpliftId, link.WorkOrderId))) continue; if (!byWorkOrder.TryGetValue(link.WorkOrderId, out var group)) { group = new List(); byWorkOrder[link.WorkOrderId] = group; } group.Add(link); } var result = new Dictionary(byWorkOrder.Count); foreach (var (workOrderId, group) in byWorkOrder) { var pendingCount = 0; UpliftLinkRow? primary = null; foreach (var item in group) { if (item.Status == "Pending") pendingCount++; if (primary == null || IsNewer(item.CreatedDate, primary.CreatedDate)) primary = item; } result[workOrderId] = new BoardUpliftAgg( pendingCount, HasUplift: true, primary?.Status, primary?.RequestedNTE); } return result; } private static bool IsNewer(DateTime? candidate, DateTime? current) { if (candidate == current) return false; if (candidate is null) return false; if (current is null) return true; return candidate.Value > current.Value; } private sealed record UpliftLinkRow( int UpliftId, int WorkOrderId, string Status, decimal RequestedNTE, DateTime? CreatedDate); private sealed record BoardUpliftAgg( int PendingCount, bool HasUplift, string? PrimaryStatus, decimal? PrimaryAmount); } }