diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs index f12d774..cd4122f 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs @@ -36,6 +36,13 @@ namespace SeaHaven.DataServices.Helpers w.WorkOrderType, w.ServiceId, w.ServiceNameSnapshot, + ServiceDisplayName = w.ServiceId.HasValue + ? (w.ServiceDefinition != null + && w.ServiceDefinition.IsActive + && w.ServiceDefinition.IsDeleted != true + ? w.ServiceDefinition.Name + : w.ServiceNameSnapshot ?? w.Trade ?? w.Problem) + : w.Trade ?? w.Problem, w.SiteCode, LocationName = w.Locations != null ? w.Locations.Name : null, SiteNotes = w.Locations != null ? w.Locations.Notes : null, @@ -180,7 +187,7 @@ namespace SeaHaven.DataServices.Helpers technicianAssigned == false ? null : frozenCompany?.ContactName ?? w.TechName, technicianAssigned == false ? null : frozenCompany?.Phone ?? techPhone, w.DispatchApptDate, - w.Trade ?? w.Problem, + w.ServiceDisplayName, w.Problem, w.ServiceNotes, w.ExtraServices, diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs index 6557db2..e7b3cc1 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs @@ -264,8 +264,14 @@ namespace SeaHaven.DataServices.Helpers return query.Where(w => normalized.Any(pm => - (w.Trade ?? "").ToLower().Contains(pm) - || (w.Problem ?? "").ToLower().Contains(pm))); + w.ServiceId.HasValue + ? (w.ServiceDefinition != null + && w.ServiceDefinition.IsActive + && w.ServiceDefinition.IsDeleted != true + ? (w.ServiceDefinition.Name ?? "").ToLower().Contains(pm) + : (w.ServiceNameSnapshot ?? w.Trade ?? w.Problem ?? "").ToLower().Contains(pm)) + : (w.Trade ?? "").ToLower().Contains(pm) + || (w.Problem ?? "").ToLower().Contains(pm))); } /// diff --git a/SeaHavenIndustries.Tests/WorkOrderServiceRegistryFilterRelationalTests.cs b/SeaHavenIndustries.Tests/WorkOrderServiceRegistryFilterRelationalTests.cs new file mode 100644 index 0000000..b9a6e0a --- /dev/null +++ b/SeaHavenIndustries.Tests/WorkOrderServiceRegistryFilterRelationalTests.cs @@ -0,0 +1,159 @@ +using Data.SeaHavenIndustries; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Helpers; +using SeaHaven.Services.Implementation; + +namespace SeaHavenIndustries.Tests; + +public sealed class WorkOrderServiceRegistryFilterRelationalTests +{ + [Fact] + public async Task ServiceDisplayAndFilter_UseCurrentActiveNameAndKeepHistoricalAndLegacyLabels() + { + await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True"); + await connection.OpenAsync(); + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + await using var context = new SqliteServiceRegistryTestDbContext(options); + await context.Database.EnsureCreatedAsync(); + + context.Services.AddRange( + new Service + { + Id = 10, + Name = "Current Active Name", + NormalizedName = "CURRENT ACTIVE NAME", + Trade = "Electrical", + IconKey = "electrical", + IsActive = true + }, + new Service + { + Id = 20, + Name = "Current Inactive Name", + NormalizedName = "CURRENT INACTIVE NAME", + Trade = "Electrical", + IconKey = "electrical", + IsActive = false + }); + context.workOrders.AddRange( + new WorkOrder + { + Id = 1, + ServiceId = 10, + ServiceNameSnapshot = "Original Active Name", + Trade = "Original Active Name" + }, + new WorkOrder + { + Id = 2, + ServiceId = 20, + ServiceNameSnapshot = "Historical Inactive Name", + Trade = "Historical Inactive Name" + }, + new WorkOrder + { + Id = 3, + Trade = "Legacy Cooling", + Problem = "Legacy Leak" + }); + await context.SaveChangesAsync(); + + var activeRows = await WorkOrderBoardProjection.ProjectRowsAsync( + context, + context.workOrders.AsNoTracking().Where(w => w.Id == 1), + isUnscheduled: true); + var activeDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(activeRows), DateTime.UtcNow); + + var inactiveRows = await WorkOrderBoardProjection.ProjectRowsAsync( + context, + context.workOrders.AsNoTracking().Where(w => w.Id == 2), + isUnscheduled: true); + var inactiveDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(inactiveRows), DateTime.UtcNow); + + var legacyRows = await WorkOrderBoardProjection.ProjectRowsAsync( + context, + context.workOrders.AsNoTracking().Where(w => w.Id == 3), + isUnscheduled: true); + var legacyDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(legacyRows), DateTime.UtcNow); + + var currentActiveNameMatches = await WorkOrderBoardQueryFilters + .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Current Active Name" }) + .Select(w => w.Id) + .ToListAsync(); + + var historicalInactiveNameMatches = await WorkOrderBoardQueryFilters + .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Historical Inactive Name" }) + .Select(w => w.Id) + .ToListAsync(); + + var inactiveRegistryNameMatches = await WorkOrderBoardQueryFilters + .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Current Inactive Name" }) + .Select(w => w.Id) + .ToListAsync(); + + var legacyTradeMatches = await WorkOrderBoardQueryFilters + .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Legacy Cooling" }) + .Select(w => w.Id) + .ToListAsync(); + + var legacyProblemMatches = await WorkOrderBoardQueryFilters + .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Legacy Leak" }) + .Select(w => w.Id) + .ToListAsync(); + + var actual = string.Join("\n", new[] + { + $"active-display={activeDisplay.Pm}", + $"inactive-display={inactiveDisplay.Pm}", + $"legacy-display={legacyDisplay.Pm}", + $"active-filter={string.Join(',', currentActiveNameMatches)}", + $"inactive-snapshot-filter={string.Join(',', historicalInactiveNameMatches)}", + $"inactive-current-name-filter={string.Join(',', inactiveRegistryNameMatches)}", + $"legacy-trade-filter={string.Join(',', legacyTradeMatches)}", + $"legacy-problem-filter={string.Join(',', legacyProblemMatches)}" + }); + var expected = string.Join("\n", new[] + { + "active-display=Current Active Name", + "inactive-display=Historical Inactive Name", + "legacy-display=Legacy Cooling", + "active-filter=1", + "inactive-snapshot-filter=2", + "inactive-current-name-filter=", + "legacy-trade-filter=3", + "legacy-problem-filter=3" + }); + Assert.True(expected == actual, $"Expected:\n{expected}\nActual:\n{actual}"); + } + + private sealed class SqliteServiceRegistryTestDbContext : ApplicationDbContext + { + public SqliteServiceRegistryTestDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + // SQL Server filtered indexes and rowversion semantics are not supported by SQLite. + foreach (var index in builder.Model.GetEntityTypes().SelectMany(entity => entity.GetIndexes())) + { + if (index.GetFilter() != null) + index.SetFilter(null); + } + + foreach (var entityType in new[] { typeof(WorkOrder), typeof(Dispatch) }) + { + var property = builder.Entity(entityType).Property("RowVersion").Metadata; + property.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never; + property.IsConcurrencyToken = false; + } + } + } +}