From 8efe4460bb3e3011181ff824cd91c3e774533eb9 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Tue, 7 Jul 2026 10:11:04 -0300 Subject: [PATCH] fix(work-orders): complete Phase 1 board indexes, Service DTO and helper wiring Add AssignTo+ScheduledDate and WorkOrderType indexes, expose service on board rows, and route data access through shared projection/filter helpers. --- .../20260701120000_Phase1_BoardIndexes.cs | 28 +++ .../ApplicationDbContextModelSnapshot.cs | 4 + .../Helpers/WorkOrderBoardProjection.cs | 4 +- .../Helpers/WorkOrderBoardQueryFilters.cs | 19 ++ .../WorkOrderBoardDataService.cs | 172 ++---------------- .../Interfaces/WorkOrderBoardModels.cs | 3 +- SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs | 2 +- .../Implementation/WorkOrderBoardService.cs | 2 +- 8 files changed, 69 insertions(+), 165 deletions(-) diff --git a/Data.SeaHavenIndustries/Migrations/20260701120000_Phase1_BoardIndexes.cs b/Data.SeaHavenIndustries/Migrations/20260701120000_Phase1_BoardIndexes.cs index 8b0d794..23968a8 100644 --- a/Data.SeaHavenIndustries/Migrations/20260701120000_Phase1_BoardIndexes.cs +++ b/Data.SeaHavenIndustries/Migrations/20260701120000_Phase1_BoardIndexes.cs @@ -18,6 +18,22 @@ namespace Data.SeaHavenIndustries.Migrations BEGIN CREATE INDEX IX_workOrders_ScheduledDate ON workOrders (ScheduledDate); END + + IF NOT EXISTS ( + SELECT 1 FROM sys.indexes + WHERE name = 'IX_workOrders_AssignTo_ScheduledDate' + AND object_id = OBJECT_ID('workOrders')) + BEGIN + CREATE INDEX IX_workOrders_AssignTo_ScheduledDate ON workOrders (AssignTo, ScheduledDate); + END + + IF NOT EXISTS ( + SELECT 1 FROM sys.indexes + WHERE name = 'IX_workOrders_WorkOrderType' + AND object_id = OBJECT_ID('workOrders')) + BEGIN + CREATE INDEX IX_workOrders_WorkOrderType ON workOrders (WorkOrderType); + END """); } @@ -25,6 +41,18 @@ namespace Data.SeaHavenIndustries.Migrations protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.Sql(""" + IF EXISTS ( + SELECT 1 FROM sys.indexes + WHERE name = 'IX_workOrders_WorkOrderType' + AND object_id = OBJECT_ID('workOrders')) + DROP INDEX IX_workOrders_WorkOrderType ON workOrders; + + IF EXISTS ( + SELECT 1 FROM sys.indexes + WHERE name = 'IX_workOrders_AssignTo_ScheduledDate' + AND object_id = OBJECT_ID('workOrders')) + DROP INDEX IX_workOrders_AssignTo_ScheduledDate ON workOrders; + IF EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_workOrders_ScheduledDate' diff --git a/Data.SeaHavenIndustries/Migrations/ApplicationDbContextModelSnapshot.cs b/Data.SeaHavenIndustries/Migrations/ApplicationDbContextModelSnapshot.cs index b4bdf21..061ccd8 100644 --- a/Data.SeaHavenIndustries/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Data.SeaHavenIndustries/Migrations/ApplicationDbContextModelSnapshot.cs @@ -2031,12 +2031,16 @@ namespace Data.SeaHavenIndustries.Migrations b.HasIndex("AssignTo"); + b.HasIndex("AssignTo", "ScheduledDate"); + b.HasIndex("LocationId"); b.HasIndex("PrimaryDispatchId"); b.HasIndex("ScheduledDate"); + b.HasIndex("WorkOrderType"); + b.ToTable("workOrders"); }); diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs index f862e1e..3b2ebac 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs @@ -56,6 +56,7 @@ namespace SeaHaven.DataServices.Helpers ? w.PrimaryDispatch.Vendor.Phone : null, DispatchApptDate = w.PrimaryDispatch != null ? w.PrimaryDispatch.ScheduledDate : null, + w.Service, w.Trade, w.Problem, w.DocStatus, @@ -92,8 +93,7 @@ namespace SeaHaven.DataServices.Helpers w.TechName, w.TechPhone, w.DispatchApptDate, - w.Trade ?? w.Problem, - w.Problem, + w.Service ?? w.Trade ?? w.Problem, w.DocStatus, w.RowVersion, isUnscheduled)).ToList(); diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs index bb9919b..35465ca 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs @@ -134,5 +134,24 @@ namespace SeaHaven.DataServices.Helpers && w.TargetWeek >= dateFrom && w.TargetWeek <= dateTo)); } + + public static IQueryable ApplySearchFilter(IQueryable query, string? search) + { + if (string.IsNullOrWhiteSpace(search)) + return query; + + var s = search.Trim().ToLower(); + return query.Where(w => + (w.SiteCode ?? "").ToLower().Contains(s) + || (w.InternalWONumber ?? "").ToLower().Contains(s) + || (w.WorkerOrderNumber ?? "").ToLower().Contains(s) + || (w.Locations != null && (w.Locations.Name ?? "").ToLower().Contains(s)) + || (w.AssignToUser != null && ((w.AssignToUser.FirstName ?? "") + " " + (w.AssignToUser.LastName ?? "")).ToLower().Contains(s)) + || (w.Service ?? "").ToLower().Contains(s) + || (w.Trade ?? "").ToLower().Contains(s) + || (w.Problem ?? "").ToLower().Contains(s) + || (w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null && (w.PrimaryDispatch.Vendor.CompanyName ?? "").ToLower().Contains(s)) + || (w.Status ?? "").ToLower().Contains(s)); + } } } diff --git a/SeaHaven.DataServices/Implementation/WorkOrderBoardDataService.cs b/SeaHaven.DataServices/Implementation/WorkOrderBoardDataService.cs index e2f1bac..8fd0422 100644 --- a/SeaHaven.DataServices/Implementation/WorkOrderBoardDataService.cs +++ b/SeaHaven.DataServices/Implementation/WorkOrderBoardDataService.cs @@ -1,6 +1,7 @@ using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Helpers; using SeaHaven.DataServices.Interfaces; namespace SeaHaven.DataServices.Implementation @@ -19,12 +20,10 @@ namespace SeaHaven.DataServices.Implementation var weekStartDate = query.WeekStart.ToDateTime(TimeOnly.MinValue); var weekEndDate = query.WeekEnd.ToDateTime(TimeOnly.MinValue); - var baseQuery = _context.workOrders - .AsNoTracking() - .Where(w => w.istemplate != true && (w.IsDeleted != true || w.IsDeleted == null)); - - baseQuery = ApplyDispatcherFilter(baseQuery, query); - baseQuery = ApplyTypeFilter(baseQuery, query); + var baseQuery = WorkOrderBoardQueryFilters.ApplyBaseScope(_context.workOrders.AsNoTracking()); + baseQuery = WorkOrderBoardQueryFilters.ApplyDispatcherFilter( + baseQuery, query.Dispatchers, query.MyWorkOrders, query.CurrentUserId); + baseQuery = WorkOrderBoardQueryFilters.ApplyTypeFilter(baseQuery, query.Types); var scheduledBase = baseQuery.Where(w => (w.ScheduledDate != null @@ -41,12 +40,16 @@ namespace SeaHaven.DataServices.Implementation if (!string.IsNullOrWhiteSpace(query.Search)) { - scheduledBase = ApplySearchFilter(scheduledBase, query.Search); - unscheduledBase = ApplySearchFilter(unscheduledBase, query.Search); + scheduledBase = WorkOrderBoardQueryFilters.ApplySearchFilter(scheduledBase, query.Search); + unscheduledBase = WorkOrderBoardQueryFilters.ApplySearchFilter(unscheduledBase, query.Search); } - var scheduledRows = await ProjectRows(scheduledBase, isUnscheduled: false); - var unscheduledRows = await ProjectRows(unscheduledBase, isUnscheduled: true); + var scheduledRows = await WorkOrderBoardProjection.ProjectRowsAsync( + scheduledBase.OrderBy(w => w.ScheduledDate).ThenBy(w => w.InternalWONumber), + isUnscheduled: false); + var unscheduledRows = await WorkOrderBoardProjection.ProjectRowsAsync( + unscheduledBase.OrderBy(w => w.InternalWONumber), + isUnscheduled: true); return new WorkOrderBoardQueryResult(scheduledRows, unscheduledRows, scheduledTotal); } @@ -66,154 +69,5 @@ namespace SeaHaven.DataServices.Implementation u.Color)) .ToListAsync(); } - - private static IQueryable ApplyDispatcherFilter(IQueryable query, WorkOrderBoardQuery boardQuery) - { - if (boardQuery.MyWorkOrders && !string.IsNullOrEmpty(boardQuery.CurrentUserId)) - return query.Where(w => w.AssignTo == boardQuery.CurrentUserId); - - if (boardQuery.Dispatchers == null || boardQuery.Dispatchers.Count == 0) - return query; - - var ids = boardQuery.Dispatchers - .Where(d => d != "__unassigned__") - .ToList(); - var includeUnassigned = boardQuery.Dispatchers.Contains("__unassigned__"); - - if (includeUnassigned && ids.Count == 0) - return query.Where(w => w.AssignTo == null || w.AssignTo == ""); - - if (includeUnassigned && ids.Count > 0) - { - return query.Where(w => - w.AssignTo == null || w.AssignTo == "" || ids.Contains(w.AssignTo)); - } - - return query.Where(w => w.AssignTo != null && ids.Contains(w.AssignTo)); - } - - private static IQueryable ApplyTypeFilter(IQueryable query, WorkOrderBoardQuery boardQuery) - { - if (boardQuery.Types == null || boardQuery.Types.Count == 0) - return query; - - return query.Where(w => w.WorkOrderType != null && boardQuery.Types.Contains(w.WorkOrderType.Value)); - } - - private static IQueryable ApplySearchFilter(IQueryable query, string search) - { - var s = search.Trim().ToLower(); - return query.Where(w => - (w.SiteCode ?? "").ToLower().Contains(s) - || (w.InternalWONumber ?? "").ToLower().Contains(s) - || (w.WorkerOrderNumber ?? "").ToLower().Contains(s) - || (w.Locations != null && (w.Locations.Name ?? "").ToLower().Contains(s)) - || (w.AssignToUser != null && ((w.AssignToUser.FirstName ?? "") + " " + (w.AssignToUser.LastName ?? "")).ToLower().Contains(s)) - || (w.Trade ?? "").ToLower().Contains(s) - || (w.Problem ?? "").ToLower().Contains(s) - || (w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null && (w.PrimaryDispatch.Vendor.CompanyName ?? "").ToLower().Contains(s)) - || (w.Status ?? "").ToLower().Contains(s)); - } - - private async Task> ProjectRows( - IQueryable query, - bool isUnscheduled) - { - var rows = await query - .Include(w => w.AssignToUser) - .Include(w => w.Locations) - .Include(w => w.PrimaryDispatch!) - .ThenInclude(d => d!.Vendor) - .Include(w => w.WorkOrderContacts!) - .ThenInclude(c => c.POC) - .OrderBy(w => w.ScheduledDate) - .ThenBy(w => w.InternalWONumber) - .Select(w => new - { - w.Id, - w.InternalWONumber, - w.RescheduleCount, - w.CarriedOver, - w.WorkOrderType, - w.SiteCode, - LocationName = w.Locations != null ? w.Locations.Name : null, - Poc = w.WorkOrderContacts! - .OrderBy(c => c.Id) - .Select(c => new - { - Name = c.POC != null - ? ((c.POC.FirstName ?? "") + " " + (c.POC.LastName ?? "")).Trim() - : null, - c.POC!.PhoneNumber, - 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, - 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, - TechPhone = 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.DocStatus, - w.RowVersion - }) - .ToListAsync(); - - return rows.Select(w => new WorkOrderBoardRawRow( - w.Id, - w.InternalWONumber, - w.RescheduleCount, - w.CarriedOver, - w.WorkOrderType, - w.SiteCode, - w.LocationName, - w.Poc?.Name, - w.Poc?.PhoneNumber, - w.Poc?.Notes, - 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, - w.VendorId, - w.VendorName, - w.TechName, - w.TechPhone, - w.DispatchApptDate, - w.Trade ?? w.Problem, - w.Problem, - w.DocStatus, - w.RowVersion, - isUnscheduled)).ToList(); - } } } diff --git a/SeaHaven.DataServices/Interfaces/WorkOrderBoardModels.cs b/SeaHaven.DataServices/Interfaces/WorkOrderBoardModels.cs index 4b96481..44fb01b 100644 --- a/SeaHaven.DataServices/Interfaces/WorkOrderBoardModels.cs +++ b/SeaHaven.DataServices/Interfaces/WorkOrderBoardModels.cs @@ -40,8 +40,7 @@ namespace SeaHaven.DataServices.Interfaces string? TechName, string? TechPhone, DateTime? DispatchApptDate, - string? Trade, - string? Problem, + string? Service, DocStatus? DocStatus, byte[]? RowVersion, bool IsUnscheduled); diff --git a/SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs b/SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs index 674097c..8e34bfb 100644 --- a/SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs +++ b/SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs @@ -49,7 +49,7 @@ namespace SeaHaven.Services.DTOs public string? TechPhone { get; set; } public DateTime? ApptDate { get; set; } public string? ApptTime { get; set; } - public string? Pm { get; set; } + public string? Service { get; set; } public DocStatus? DocStatus { get; set; } public byte[]? RowVersion { get; set; } } diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardService.cs index a4b6001..d123f4b 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardService.cs @@ -108,7 +108,7 @@ namespace SeaHaven.Services.Implementation TechPhone = row.TechPhone, ApptDate = row.DispatchApptDate ?? row.ScheduledDate, ApptTime = WorkOrderDerivedFields.FormatApptTime(apptStart, apptEnd), - Pm = row.Trade ?? row.Problem, + Service = row.Service, DocStatus = row.DocStatus, RowVersion = row.RowVersion };