mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
fix(board-search): keep undated rows for clients that omit includeDateless (SH-391)
The strict date range made undated open work orders disappear for every client that predates the includeDateless flag. The frontend on main sends the all-weeks window (2000-01-01..2099-12-31) for "no range", the 1970-01-01..2099-12-31 window for the pinned Unassigned queue, and no date input at all for the WO# duplicate lookup. None of those send the flag, so deploying this backend before the frontend would have dropped undated WOs from all three flows. includeDateless is now optional. An explicit value still wins. When it is omitted, a request with no date input or with an all-weeks Custom window keeps its open undated rows, as before SH-391. Any other range stays strict. The backend and frontend can therefore deploy in either order.
This commit is contained in:
parent
9d5798437d
commit
67cd9c589b
4 changed files with 131 additions and 4 deletions
|
|
@ -90,9 +90,11 @@ namespace SeaHaven.Services.DTOs
|
|||
/// <summary>
|
||||
/// When true, rows with no date (no Schedule On) are returned alongside
|
||||
/// the in-range rows. Sent for searches with no date range selected; an
|
||||
/// explicit range leaves it false so the range strictly narrows (SH-391).
|
||||
/// explicit range leaves it unset or false so the range strictly narrows
|
||||
/// (SH-391). When it is omitted, clients that predate the flag keep their
|
||||
/// undated rows: see <see cref="WorkOrderSearchDateRangeResolver.ResolveIncludeDateless"/>.
|
||||
/// </summary>
|
||||
public bool IncludeDateless { get; set; }
|
||||
public bool? IncludeDateless { get; set; }
|
||||
public List<string>? Sites { get; set; }
|
||||
public List<string>? Regions { get; set; }
|
||||
public List<WorkOrderType>? Types { get; set; }
|
||||
|
|
|
|||
|
|
@ -15,6 +15,36 @@ namespace SeaHaven.Services.Helpers
|
|||
|
||||
public static class WorkOrderSearchDateRangeResolver
|
||||
{
|
||||
// Widest windows the frontend sends for "every week": the no-range search
|
||||
// (ADVANCED_SEARCH_ALL_WEEKS_FROM/TO, 2000-01-01..2099-12-31) and the pinned
|
||||
// Unassigned queue (UNASSIGNED_QUEUE_DATE_FROM/TO, 1970-01-01..2099-12-31).
|
||||
private static readonly DateOnly AllWeeksFrom = new(2000, 1, 1);
|
||||
private static readonly DateOnly AllWeeksTo = new(2099, 12, 31);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a board search adds open undated rows to its date range (SH-391).
|
||||
/// An explicit <paramref name="includeDateless"/> wins. When it is omitted,
|
||||
/// as by clients that predate the flag, a search with no date input or with
|
||||
/// an all-weeks Custom window still returns undated rows, as before SH-391:
|
||||
/// those requests mean "no range selected". Any other range is strict.
|
||||
/// </summary>
|
||||
public static bool ResolveIncludeDateless(
|
||||
bool? includeDateless,
|
||||
WorkOrderAdvancedSearchDatePreset? preset,
|
||||
DateOnly? dateFrom,
|
||||
DateOnly? dateTo)
|
||||
{
|
||||
if (includeDateless.HasValue)
|
||||
return includeDateless.Value;
|
||||
|
||||
if (!preset.HasValue && !dateFrom.HasValue && !dateTo.HasValue)
|
||||
return true;
|
||||
|
||||
return preset == WorkOrderAdvancedSearchDatePreset.Custom
|
||||
&& dateFrom <= AllWeeksFrom
|
||||
&& dateTo >= AllWeeksTo;
|
||||
}
|
||||
|
||||
public static WorkOrderSearchDateRange Resolve(
|
||||
WorkOrderAdvancedSearchDatePreset? preset,
|
||||
DateOnly? dateFrom,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,11 @@ namespace SeaHaven.Services.Implementation
|
|||
sortBy,
|
||||
query.SortDir ?? "asc",
|
||||
accountId,
|
||||
query.IncludeDateless);
|
||||
WorkOrderSearchDateRangeResolver.ResolveIncludeDateless(
|
||||
query.IncludeDateless,
|
||||
query.DatePreset,
|
||||
query.DateFrom,
|
||||
query.DateTo));
|
||||
|
||||
var result = await _searchDataService.SearchAsync(dataQuery);
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Globalization;
|
||||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
|
@ -55,7 +56,7 @@ public class WorkOrderAdvancedSearchDateRangeTests
|
|||
return wo;
|
||||
}
|
||||
|
||||
private static WorkOrderAdvancedSearchQueryDto UnassignedInRange(bool includeDateless = false) => new()
|
||||
private static WorkOrderAdvancedSearchQueryDto UnassignedInRange(bool? includeDateless = null) => new()
|
||||
{
|
||||
DatePreset = WorkOrderAdvancedSearchDatePreset.Custom,
|
||||
DateFrom = RangeFrom,
|
||||
|
|
@ -149,6 +150,96 @@ public class WorkOrderAdvancedSearchDateRangeTests
|
|||
Assert.Equal(new[] { 1, 3 }, result.Items.Select(i => i.Id).OrderBy(id => id).ToArray());
|
||||
}
|
||||
|
||||
// Payloads the frontend sent before includeDateless existed. They must keep
|
||||
// their undated rows so this backend can deploy ahead of the client.
|
||||
[Theory]
|
||||
[InlineData(2000)] // no range selected: ADVANCED_SEARCH_ALL_WEEKS_FROM
|
||||
[InlineData(1970)] // pinned Unassigned queue: UNASSIGNED_QUEUE_DATE_FROM
|
||||
public async Task SearchAsync_AllWeeksWindowWithoutFlag_KeepsOpenUndatedRows(int fromYear)
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.workOrders.AddRange(
|
||||
Wo(1, new DateTime(2026, 8, 4)),
|
||||
Wo(2, null, status: LifecycleStatus.Incomplete),
|
||||
Wo(3, null, status: LifecycleStatus.Completed));
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await CreateService(context).SearchAsync(new WorkOrderAdvancedSearchQueryDto
|
||||
{
|
||||
DatePreset = WorkOrderAdvancedSearchDatePreset.Custom,
|
||||
DateFrom = new DateOnly(fromYear, 1, 1),
|
||||
DateTo = new DateOnly(2099, 12, 31),
|
||||
Dispatchers = new List<string> { "__unassigned__" },
|
||||
PageSize = 50
|
||||
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
||||
|
||||
Assert.Equal(new[] { 1, 2 }, result.Items.Select(i => i.Id).OrderBy(id => id).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_NoDateInputWithoutFlag_KeepsOpenUndatedRows()
|
||||
{
|
||||
// The WO# duplicate lookup sends only the search text and paging.
|
||||
await using var context = CreateContext();
|
||||
var thisWeek = WorkOrderSearchDateRangeResolver.GetIsoWeekStart(DateOnly.FromDateTime(DateTime.UtcNow));
|
||||
context.workOrders.AddRange(
|
||||
Wo(1, thisWeek.ToDateTime(TimeOnly.MinValue)),
|
||||
Wo(2, null, status: LifecycleStatus.Incomplete),
|
||||
Wo(3, thisWeek.AddDays(-7).ToDateTime(TimeOnly.MinValue)));
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await CreateService(context).SearchAsync(
|
||||
new WorkOrderAdvancedSearchQueryDto { PageSize = 100 },
|
||||
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
||||
null);
|
||||
|
||||
Assert.Equal(new[] { 1, 2 }, result.Items.Select(i => i.Id).OrderBy(id => id).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_AllWeeksWindowWithFlagFalse_ExcludesUndatedRows()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.workOrders.AddRange(
|
||||
Wo(1, new DateTime(2026, 8, 4)),
|
||||
Wo(2, null, status: LifecycleStatus.Incomplete));
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await CreateService(context).SearchAsync(new WorkOrderAdvancedSearchQueryDto
|
||||
{
|
||||
DatePreset = WorkOrderAdvancedSearchDatePreset.Custom,
|
||||
DateFrom = new DateOnly(2000, 1, 1),
|
||||
DateTo = new DateOnly(2099, 12, 31),
|
||||
IncludeDateless = false,
|
||||
PageSize = 50
|
||||
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
||||
|
||||
Assert.Equal(new[] { 1 }, result.Items.Select(i => i.Id).ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, null, null, null, true)]
|
||||
[InlineData(null, WorkOrderAdvancedSearchDatePreset.Custom, "2000-01-01", "2099-12-31", true)]
|
||||
[InlineData(null, WorkOrderAdvancedSearchDatePreset.Custom, "1970-01-01", "2099-12-31", true)]
|
||||
[InlineData(null, WorkOrderAdvancedSearchDatePreset.Custom, "2000-01-02", "2099-12-31", false)]
|
||||
[InlineData(null, WorkOrderAdvancedSearchDatePreset.Custom, "2026-09-21", "2026-09-25", false)]
|
||||
[InlineData(null, WorkOrderAdvancedSearchDatePreset.ThisWeek, null, null, false)]
|
||||
[InlineData(false, null, null, null, false)]
|
||||
[InlineData(true, WorkOrderAdvancedSearchDatePreset.ThisWeek, null, null, true)]
|
||||
public void ResolveIncludeDateless_InfersOnlyForNoRangeRequests(
|
||||
bool? flag,
|
||||
WorkOrderAdvancedSearchDatePreset? preset,
|
||||
string? from,
|
||||
string? to,
|
||||
bool expected)
|
||||
{
|
||||
Assert.Equal(expected, WorkOrderSearchDateRangeResolver.ResolveIncludeDateless(
|
||||
flag,
|
||||
preset,
|
||||
from is null ? null : DateOnly.Parse(from, CultureInfo.InvariantCulture),
|
||||
to is null ? null : DateOnly.Parse(to, CultureInfo.InvariantCulture)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_RangeForAccountUser_NeverReturnsAnotherAccountsRows()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue