mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
An explicit date range let every undated, non-terminal work order through, so Unassigned plus a range still returned all ~1,780 unassigned rows. The range now matches Schedule On, or the Target Week for week-only rows (overlap), the same date the weekly board groups by. Undated rows are added only when the caller sends includeDateless, which the client uses for searches with no range selected and for the Unassigned queue.
1538 lines
58 KiB
C#
1538 lines
58 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Helpers;
|
|
using SeaHaven.DataServices.Implementation;
|
|
|
|
namespace SeaHavenIndustries.Tests;
|
|
|
|
public class WorkOrderBoardSearchFilterTests
|
|
{
|
|
private static ApplicationDbContext CreateContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new ApplicationDbContext(options);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("BK5", 1)]
|
|
[InlineData("10000000001", 1)]
|
|
[InlineData("HVAC Tech", 1)]
|
|
[InlineData("Acme HVAC", 1)]
|
|
[InlineData("sched", 1)]
|
|
[InlineData("Leak repair", 1)]
|
|
[InlineData("Reactive", 1)]
|
|
[InlineData("6/23", 1)]
|
|
public async Task Apply_MatchesExpectedField(string search, int expectedId)
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedSearchFixtures(context);
|
|
|
|
var ids = await WorkOrderBoardSearchFilter
|
|
.Apply(context.workOrders.AsQueryable(), search)
|
|
.Select(w => w.Id)
|
|
.ToListAsync();
|
|
|
|
Assert.Contains(expectedId, ids);
|
|
}
|
|
|
|
[Fact]
|
|
public void NormalizeSearch_IgnoresSingleCharacter()
|
|
{
|
|
Assert.Null(WorkOrderBoardSearchFilter.NormalizeSearch("a"));
|
|
Assert.Null(WorkOrderBoardSearchFilter.NormalizeSearch(" "));
|
|
Assert.Equal("ab", WorkOrderBoardSearchFilter.NormalizeSearch(" ab "));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Apply_LifecycleLabel_MatchesScheduled()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedSearchFixtures(context);
|
|
|
|
var ids = await WorkOrderBoardSearchFilter
|
|
.Apply(context.workOrders.AsQueryable(), "scheduled")
|
|
.Select(w => w.Id)
|
|
.ToListAsync();
|
|
|
|
Assert.Single(ids);
|
|
Assert.Equal(1, ids[0]);
|
|
}
|
|
|
|
private static async Task SeedSearchFixtures(ApplicationDbContext context)
|
|
{
|
|
context.Vendors.Add(new Vendor
|
|
{
|
|
Id = 1,
|
|
CompanyName = "Acme HVAC",
|
|
ContactName = "HVAC Tech",
|
|
Phone = "555-0100"
|
|
});
|
|
context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1 });
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 1,
|
|
SiteCode = "BK5",
|
|
InternalWONumber = "10000000001",
|
|
WorkerOrderTitle = "Leak repair",
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
PrimaryDispatchId = 10
|
|
});
|
|
await context.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
public class WorkOrderBoardSearchCountsTests
|
|
{
|
|
private static ApplicationDbContext CreateContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new ApplicationDbContext(options);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetBoardRows_SearchReducesScheduledAndKeepsTotal()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
SiteCode = "BK5",
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
SiteCode = "BK6",
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
SiteCode = "BK5",
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new WorkOrderBoardDataService(context);
|
|
var result = await service.GetBoardRowsAsync(new SeaHaven.DataServices.Interfaces.WorkOrderBoardQuery(
|
|
weekStart,
|
|
weekStart.AddDays(4),
|
|
null,
|
|
false,
|
|
null,
|
|
false,
|
|
"BK5",
|
|
null));
|
|
|
|
Assert.Equal(2, result.ScheduledTotalBeforeSearch);
|
|
Assert.Single(result.ScheduledRows);
|
|
Assert.Equal("BK5", result.ScheduledRows[0].SiteCode);
|
|
Assert.Empty(result.UnscheduledRows);
|
|
}
|
|
}
|
|
|
|
public class WorkOrderSearchDateRangeResolverTests
|
|
{
|
|
[Fact]
|
|
public void Resolve_ThisWeek_UsesIsoMonday()
|
|
{
|
|
var reference = new DateOnly(2026, 6, 24);
|
|
var range = SeaHaven.Services.Helpers.WorkOrderSearchDateRangeResolver.Resolve(
|
|
SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
|
|
null,
|
|
null,
|
|
reference);
|
|
|
|
Assert.Equal(new DateOnly(2026, 6, 22), range.From);
|
|
Assert.Equal(new DateOnly(2026, 6, 26), range.To);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_Custom_RequiresDates()
|
|
{
|
|
var ex = Assert.Throws<ArgumentException>(() =>
|
|
SeaHaven.Services.Helpers.WorkOrderSearchDateRangeResolver.Resolve(
|
|
SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
null,
|
|
null));
|
|
|
|
Assert.Contains("dateFrom", ex.Message);
|
|
}
|
|
}
|
|
|
|
public class WorkOrderAdvancedSearchServiceTests
|
|
{
|
|
private static ApplicationDbContext CreateContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new ApplicationDbContext(options);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_FiltersBySiteAndPaginates()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
SiteCode = "BK5",
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
SiteCode = "BK6",
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(6),
|
|
Sites = new List<string> { "BK5" },
|
|
Page = 0,
|
|
PageSize = 50
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Single(result.Items);
|
|
Assert.Equal("BK5", result.Items.First().SiteCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_FiltersByCanonicalRegionAndUnmappedBucket()
|
|
{
|
|
await using var context = CreateContext();
|
|
var date = new DateTime(2026, 6, 23);
|
|
context.Locations.AddRange(
|
|
new Locations { Id = 1, State = "WA" },
|
|
new Locations { Id = 2, State = "CALIFORNIA" },
|
|
new Locations { Id = 3, State = "Atlantis" },
|
|
new Locations { Id = 4, State = null });
|
|
context.workOrders.AddRange(
|
|
new WorkOrder { Id = 1, LocationId = 1, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
|
|
new WorkOrder { Id = 2, LocationId = 2, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
|
|
new WorkOrder { Id = 3, LocationId = 3, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
|
|
new WorkOrder { Id = 4, LocationId = 4, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled });
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
var query = new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2026, 6, 22),
|
|
DateTo = new DateOnly(2026, 6, 30),
|
|
PageSize = 50
|
|
};
|
|
|
|
query.Regions = new List<string> { "West" };
|
|
var west = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
Assert.Equal(new[] { 1 }, west.Items.Select(item => item.Id));
|
|
|
|
query.Regions = new List<string> { "California" };
|
|
var california = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
Assert.Equal(new[] { 2 }, california.Items.Select(item => item.Id));
|
|
|
|
query.Regions = new List<string> { "Unmapped/Other" };
|
|
var unmapped = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
Assert.Equal(new[] { 3, 4 }, unmapped.Items.Select(item => item.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_ClampsPageSizeToMax()
|
|
{
|
|
await using var context = CreateContext();
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
|
|
PageSize = 500
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(200, result.PageSize);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_AcceptsPageSize200()
|
|
{
|
|
await using var context = CreateContext();
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
|
|
PageSize = 200
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(200, result.PageSize);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(25)]
|
|
[InlineData(50)]
|
|
[InlineData(200)]
|
|
public async Task SearchAsync_UnassignedDispatcher_AcceptsBoardPagerPageSizes(int pageSize)
|
|
{
|
|
await using var context = CreateContext();
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2000, 1, 1),
|
|
DateTo = new DateOnly(2099, 12, 31),
|
|
Dispatchers = new List<string> { "__unassigned__" },
|
|
IncludeDateless = true,
|
|
Page = 0,
|
|
PageSize = pageSize
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(pageSize, result.PageSize);
|
|
Assert.Equal(0, result.Page);
|
|
Assert.Equal(0, result.PageBase);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnassignedDispatcher_WideCustomRange_IncludesDatedOutsideWeekAndUndated()
|
|
{
|
|
await using var context = CreateContext();
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
InternalWONumber = "10000000001",
|
|
AssignTo = null,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
InternalWONumber = "10000000002",
|
|
AssignTo = null,
|
|
ScheduledDate = new DateTime(2026, 8, 4),
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
InternalWONumber = "10000000003",
|
|
AssignTo = "",
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 4,
|
|
InternalWONumber = "10000000004",
|
|
AssignTo = "user-a",
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2000, 1, 1),
|
|
DateTo = new DateOnly(2099, 12, 31),
|
|
Dispatchers = new List<string> { "__unassigned__" },
|
|
IncludeDateless = true,
|
|
Page = 0,
|
|
PageSize = 50
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(3, result.TotalCount);
|
|
Assert.Equal(50, result.PageSize);
|
|
Assert.Contains(result.Items, i => i.Id == 1 && i.DispatcherId == null);
|
|
Assert.Contains(result.Items, i => i.Id == 2 && i.DispatcherId == null);
|
|
Assert.Contains(result.Items, i => i.Id == 3);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 4);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnassignedDispatcher_PaginatesWideCustomRange()
|
|
{
|
|
await using var context = CreateContext();
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
InternalWONumber = "10000000001",
|
|
AssignTo = null,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
InternalWONumber = "10000000002",
|
|
AssignTo = null,
|
|
ScheduledDate = new DateTime(2026, 8, 4),
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
InternalWONumber = "10000000003",
|
|
AssignTo = null,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var page0 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2000, 1, 1),
|
|
DateTo = new DateOnly(2099, 12, 31),
|
|
Dispatchers = new List<string> { "__unassigned__" },
|
|
IncludeDateless = true,
|
|
Page = 0,
|
|
PageSize = 2
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
var page1 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2000, 1, 1),
|
|
DateTo = new DateOnly(2099, 12, 31),
|
|
Dispatchers = new List<string> { "__unassigned__" },
|
|
IncludeDateless = true,
|
|
Page = 1,
|
|
PageSize = 2
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(3, page0.TotalCount);
|
|
Assert.Equal(2, page0.PageSize);
|
|
Assert.Equal(2, page0.Items.Count());
|
|
Assert.Equal(3, page1.TotalCount);
|
|
Assert.Single(page1.Items);
|
|
Assert.Empty(page0.Items.Select(i => i.Id).Intersect(page1.Items.Select(i => i.Id)));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_PageZero_ReturnsFirstPage()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
SiteCode = "BK5",
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
SiteCode = "BK6",
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(4),
|
|
Page = 0,
|
|
PageSize = 1
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(2, result.TotalCount);
|
|
Assert.Single(result.Items);
|
|
Assert.Equal(0, result.Page);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TypesOther_ReturnsOtherRowsNotPastDueSentinel()
|
|
{
|
|
await using var context = CreateContext();
|
|
var today = DateTime.UtcNow.Date;
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
ScheduledDate = today.AddDays(-3),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
WorkOrderType = WorkOrderType.Other,
|
|
ScheduledDate = today.AddDays(3),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
|
|
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
|
|
Types = new List<WorkOrderType> { WorkOrderType.Other }
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(2, result.Items.First().Id);
|
|
Assert.Equal(WorkOrderType.Other, result.Items.First().WorkOrderType);
|
|
Assert.False(result.Items.First().IsPastDue);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_OverdueTrue_ReturnsPastDueRowsOnly()
|
|
{
|
|
await using var context = CreateContext();
|
|
var today = DateTime.UtcNow.Date;
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
DueDate = today.AddDays(-3),
|
|
ScheduledDate = today.AddDays(5),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
WorkOrderType = WorkOrderType.Other,
|
|
ScheduledDate = today.AddDays(3),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
DueDate = today.AddDays(1),
|
|
ScheduledDate = today.AddDays(-5),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000003"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
|
|
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
|
|
Overdue = true
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(3, result.Items.First().Id);
|
|
Assert.True(result.Items.First().IsPastDue);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_OtherPlusOverdue_ReturnsUnion()
|
|
{
|
|
await using var context = CreateContext();
|
|
var today = DateTime.UtcNow.Date;
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
DueDate = today.AddDays(5),
|
|
ScheduledDate = today.AddDays(-3),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
WorkOrderType = WorkOrderType.Other,
|
|
ScheduledDate = today.AddDays(3),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
DueDate = today.AddDays(1),
|
|
ScheduledDate = today.AddDays(1),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000003"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
|
|
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
|
|
Types = new List<WorkOrderType> { WorkOrderType.Other },
|
|
Overdue = true
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(2, result.TotalCount);
|
|
Assert.Contains(result.Items, i => i.Id == 1 && i.IsPastDue);
|
|
Assert.Contains(result.Items, i => i.Id == 2 && i.WorkOrderType == WorkOrderType.Other);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 3);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_OverdueDerivesFromDateAndStatus_NotWorkOrderType()
|
|
{
|
|
await using var context = CreateContext();
|
|
var today = DateTime.UtcNow.Date;
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
WorkOrderType = WorkOrderType.Other,
|
|
DueDate = today.AddDays(5),
|
|
ScheduledDate = today.AddDays(-5),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
DueDate = today.AddDays(-3),
|
|
ScheduledDate = today.AddDays(5),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
DueDate = today.AddDays(-2),
|
|
ScheduledDate = today.AddDays(-2),
|
|
LifecycleStatus = LifecycleStatus.Completed,
|
|
InternalWONumber = "10000000003"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 4,
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
DueDate = today.AddDays(-1),
|
|
ScheduledDate = today.AddDays(-1),
|
|
LifecycleStatus = LifecycleStatus.Canceled,
|
|
InternalWONumber = "10000000004"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
|
|
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
|
|
Overdue = true,
|
|
Page = 0,
|
|
PageSize = 50
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
var overdue = Assert.Single(result.Items);
|
|
Assert.Equal(0, result.Page);
|
|
Assert.Equal(1, overdue.Id);
|
|
Assert.True(overdue.IsPastDue);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 2);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 3);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 4);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TreatsOtherAsRealTypeFilter_NotOverdueSentinel()
|
|
{
|
|
await using var context = CreateContext();
|
|
var today = DateTime.UtcNow.Date;
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
ScheduledDate = today.AddDays(2),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
WorkOrderType = WorkOrderType.Other,
|
|
ScheduledDate = today.AddDays(2),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
ScheduledDate = today.AddDays(-3),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000003"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
|
|
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
|
|
Types = new List<WorkOrderType> { WorkOrderType.PM, WorkOrderType.Other },
|
|
Page = 0,
|
|
PageSize = 50
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(2, result.TotalCount);
|
|
Assert.Equal(0, result.Page);
|
|
Assert.Contains(result.Items, i => i.Id == 1 && i.WorkOrderType == WorkOrderType.PM);
|
|
Assert.Contains(result.Items, i => i.Id == 2 && i.WorkOrderType == WorkOrderType.Other);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 3);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_IncludeDateless_IncludesUnscheduledRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000002"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(4),
|
|
IncludeDateless = true
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(2, result.TotalCount);
|
|
Assert.Contains(result.Items, i => i.Id == 2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_InvalidSortBy_Throws()
|
|
{
|
|
await using var context = CreateContext();
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var ex = await Assert.ThrowsAsync<ArgumentException>(() => service.SearchAsync(
|
|
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
|
|
SortBy = "invalid"
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null));
|
|
|
|
Assert.Contains("sortBy", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TypesAddOn_MatchesLegacyTypeOrIsAddOnFlag()
|
|
{
|
|
await using var context = CreateContext();
|
|
var today = DateTime.UtcNow.Date;
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
WorkOrderType = WorkOrderType.AddOn,
|
|
IsAddOn = false,
|
|
ScheduledDate = today.AddDays(1),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
IsAddOn = true,
|
|
ScheduledDate = today.AddDays(2),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002"
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
IsAddOn = false,
|
|
ScheduledDate = today.AddDays(3),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000003"
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
|
|
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
|
|
Types = new List<WorkOrderType> { WorkOrderType.AddOn }
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(2, result.TotalCount);
|
|
Assert.Contains(result.Items, i => i.Id == 1 && i.WorkOrderType == WorkOrderType.AddOn);
|
|
Assert.Contains(result.Items, i => i.Id == 2 && i.IsAddOn && i.WorkOrderType == WorkOrderType.PM);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 3);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_SeverityFilter_ReturnsEmergencyReactiveOnly()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
WorkOrderType = WorkOrderType.Emergency,
|
|
Severity = "1",
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001",
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
WorkOrderType = WorkOrderType.Reactive,
|
|
Severity = "2",
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002",
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
Severity = "1",
|
|
ScheduledDate = new DateTime(2026, 6, 25),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000003",
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(
|
|
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(6),
|
|
Severities = new List<int> { 1 },
|
|
},
|
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
|
null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(1, result.Items.Single().Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_InternalOnly_ReturnsShPrefixRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
InternalWONumber = "SH-1001",
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
InternalWONumber = "10000000002",
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(
|
|
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(6),
|
|
InternalOnly = true,
|
|
},
|
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
|
null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal("SH-1001", result.Items.Single().WoNumber);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_HasUpliftWithPendingStatus_FiltersRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" });
|
|
context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1, WorkOrderId = 1 });
|
|
context.Dispatches.Add(new Dispatch { Id = 11, VendorId = 1, WorkOrderId = 2 });
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
InternalWONumber = "10000000001",
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
PrimaryDispatchId = 10,
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
InternalWONumber = "10000000002",
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
PrimaryDispatchId = 11,
|
|
});
|
|
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
|
|
{
|
|
Id = 100,
|
|
DispatchId = 10,
|
|
RequestedNTE = 1500m,
|
|
Status = "Pending",
|
|
RequiredTier = 1,
|
|
NotificationStatus = "Pending",
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(
|
|
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(6),
|
|
HasUplift = true,
|
|
UpliftStatuses = new List<string> { "pending" },
|
|
},
|
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
|
null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(1, result.Items.Single().Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_AvetaOnly_ReturnsFlaggedRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
AvetaRequired = true,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001",
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
AvetaRequired = false,
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002",
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(
|
|
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(6),
|
|
AvetaOnly = true,
|
|
},
|
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
|
null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(1, result.Items.Single().Id);
|
|
Assert.True(result.Items.Single().AvetaRequired);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnly_ReturnsOnlyUndatedRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000002",
|
|
istemplate = false
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(4),
|
|
UnscheduledOnly = true
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Single(result.Items);
|
|
Assert.Equal(2, result.Items.Single().Id);
|
|
Assert.Null(result.Items.Single().ScheduledDate);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnlyFalse_MatchesDefaultRangeSearch()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
ScheduledDate = new DateTime(2026, 8, 4),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000003",
|
|
istemplate = false
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var withFlagFalse = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(4),
|
|
UnscheduledOnly = false
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
var withFlagAbsent = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(4)
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(withFlagAbsent.TotalCount, withFlagFalse.TotalCount);
|
|
Assert.Equal(
|
|
withFlagAbsent.Items.Select(i => i.Id).ToList(),
|
|
withFlagFalse.Items.Select(i => i.Id).ToList());
|
|
// SH-391: an explicit range narrows strictly, so the undated row 3 is out.
|
|
Assert.Equal(1, withFlagFalse.TotalCount);
|
|
Assert.Contains(withFlagFalse.Items, i => i.Id == 1);
|
|
Assert.DoesNotContain(withFlagFalse.Items, i => i.Id == 3);
|
|
Assert.DoesNotContain(withFlagFalse.Items, i => i.Id == 2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnly_IgnoresScheduledDateRange()
|
|
{
|
|
await using var context = CreateContext();
|
|
var weekStart = new DateOnly(2026, 6, 22);
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000002",
|
|
istemplate = false
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = weekStart,
|
|
DateTo = weekStart.AddDays(4),
|
|
UnscheduledOnly = true
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Single(result.Items);
|
|
Assert.Equal(2, result.Items.Single().Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnly_CustomPresetWithoutDates_DoesNotThrow()
|
|
{
|
|
await using var context = CreateContext();
|
|
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
UnscheduledOnly = true
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Equal(1, result.Items.Single().Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnly_ComposesWithAccountScopeAndSiteFacet()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
AccountId = 1,
|
|
SiteCode = "BK5",
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
AccountId = 2,
|
|
SiteCode = "BK5",
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000002",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
AccountId = 1,
|
|
SiteCode = "BK6",
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000003",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 4,
|
|
AccountId = 1,
|
|
SiteCode = "BK5",
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000004",
|
|
istemplate = false
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2026, 6, 22),
|
|
DateTo = new DateOnly(2026, 6, 26),
|
|
UnscheduledOnly = true,
|
|
Sites = new List<string> { "BK5" }
|
|
}, WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"), "disp-1");
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Single(result.Items);
|
|
Assert.Equal(1, result.Items.Single().Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnly_NoUndatedRows_ReturnsEmptyPage()
|
|
{
|
|
await using var context = CreateContext();
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
ScheduledDate = new DateTime(2026, 6, 24),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000002",
|
|
istemplate = false
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2000, 1, 1),
|
|
DateTo = new DateOnly(2099, 12, 31),
|
|
UnscheduledOnly = true,
|
|
Page = 0,
|
|
PageSize = 25
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(0, result.TotalCount);
|
|
Assert.Empty(result.Items);
|
|
Assert.Equal(0, result.Page);
|
|
Assert.Equal(25, result.PageSize);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnly_PaginatesStableOrderWithoutDuplicates()
|
|
{
|
|
await using var context = CreateContext();
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000003",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000002",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 4,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
InternalWONumber = "10000000000",
|
|
istemplate = false
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var page0 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2026, 6, 22),
|
|
DateTo = new DateOnly(2026, 6, 26),
|
|
UnscheduledOnly = true,
|
|
Page = 0,
|
|
PageSize = 2
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
var page1 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2026, 6, 22),
|
|
DateTo = new DateOnly(2026, 6, 26),
|
|
UnscheduledOnly = true,
|
|
Page = 1,
|
|
PageSize = 2
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(3, page0.TotalCount);
|
|
Assert.Equal(2, page0.Items.Count());
|
|
Assert.Equal(new[] { 2, 3 }, page0.Items.Select(i => i.Id).ToArray());
|
|
|
|
Assert.Equal(3, page1.TotalCount);
|
|
Assert.Single(page1.Items);
|
|
Assert.Equal(1, page1.Items.Single().Id);
|
|
|
|
Assert.Empty(page0.Items.Select(i => i.Id).Intersect(page1.Items.Select(i => i.Id)));
|
|
Assert.Equal(new[] { 1, 2, 3 }, page0.Items.Concat(page1.Items).Select(i => i.Id).OrderBy(id => id).ToArray());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnscheduledOnly_ExcludesCompletedAndCanceledUndatedRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
InternalWONumber = "10000000001",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Completed,
|
|
InternalWONumber = "10000000002",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
ScheduledDate = null,
|
|
LifecycleStatus = LifecycleStatus.Canceled,
|
|
InternalWONumber = "10000000003",
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 4,
|
|
ScheduledDate = new DateTime(2026, 6, 23),
|
|
LifecycleStatus = LifecycleStatus.Completed,
|
|
InternalWONumber = "10000000004",
|
|
istemplate = false
|
|
});
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
|
dataService,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
|
{
|
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
|
DateFrom = new DateOnly(2026, 6, 22),
|
|
DateTo = new DateOnly(2026, 6, 26),
|
|
UnscheduledOnly = true
|
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
|
|
|
Assert.Equal(1, result.TotalCount);
|
|
Assert.Single(result.Items);
|
|
Assert.Equal(1, result.Items.Single().Id);
|
|
Assert.Null(result.Items.Single().ScheduledDate);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 2);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 3);
|
|
Assert.DoesNotContain(result.Items, i => i.Id == 4);
|
|
}
|
|
}
|