mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-03 20:13:22 +00:00
Merge pull request #97 from Sea-Haven-Industries/fix/sh-131-clear-appt-time
fix(work-orders): persist empty apptTime as null scheduled instants
This commit is contained in:
commit
9665be0ae5
7 changed files with 204 additions and 7 deletions
|
|
@ -53,5 +53,8 @@ namespace SeaHaven.Services.Helpers
|
||||||
|
|
||||||
return date.Value.Date.Add(time.Value);
|
return date.Value.Date.Add(time.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DateTime? ToScheduledInstant(DateTime? date, TimeSpan? time)
|
||||||
|
=> time.HasValue ? CombineDateAndTime(date, time) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,8 @@ namespace SeaHaven.Services.Helpers
|
||||||
var oldStart = FormatDateTime(workOrder.ScheduledStart);
|
var oldStart = FormatDateTime(workOrder.ScheduledStart);
|
||||||
var oldEnd = FormatDateTime(workOrder.ScheduledEnd);
|
var oldEnd = FormatDateTime(workOrder.ScheduledEnd);
|
||||||
|
|
||||||
workOrder.ScheduledStart = WorkOrderBoardApptTimeParser.CombineDateAndTime(apptDate, start);
|
workOrder.ScheduledStart = WorkOrderBoardApptTimeParser.ToScheduledInstant(apptDate, start);
|
||||||
workOrder.ScheduledEnd = WorkOrderBoardApptTimeParser.CombineDateAndTime(apptDate, end);
|
workOrder.ScheduledEnd = WorkOrderBoardApptTimeParser.ToScheduledInstant(apptDate, end);
|
||||||
|
|
||||||
var newStart = FormatDateTime(workOrder.ScheduledStart);
|
var newStart = FormatDateTime(workOrder.ScheduledStart);
|
||||||
var newEnd = FormatDateTime(workOrder.ScheduledEnd);
|
var newEnd = FormatDateTime(workOrder.ScheduledEnd);
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,10 @@ namespace SeaHaven.Services.Implementation
|
||||||
public static WorkOrderBoardRowDto MapRawRow(WorkOrderBoardRawRow row, DateTime utcNow)
|
public static WorkOrderBoardRowDto MapRawRow(WorkOrderBoardRawRow row, DateTime utcNow)
|
||||||
{
|
{
|
||||||
// Appt column: date prefers dispatch appointment (vendor slot), then WO ScheduledDate.
|
// Appt column: date prefers dispatch appointment (vendor slot), then WO ScheduledDate.
|
||||||
// Time prefers WO ScheduledStart/End (dispatcher window), then dispatch datetime.
|
// Time uses WO ScheduledStart/End only. Do not fall back to a date-only dispatch
|
||||||
var apptStart = row.ScheduledStart ?? row.DispatchApptDate;
|
// instant (midnight), or clearing apptTime still renders 00:00.
|
||||||
|
var apptStart = row.ScheduledStart
|
||||||
|
?? (HasClockTime(row.DispatchApptDate) ? row.DispatchApptDate : null);
|
||||||
var apptEnd = row.ScheduledEnd;
|
var apptEnd = row.ScheduledEnd;
|
||||||
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(
|
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(
|
||||||
row.VendorId,
|
row.VendorId,
|
||||||
|
|
@ -145,6 +147,9 @@ namespace SeaHaven.Services.Implementation
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasClockTime(DateTime? value)
|
||||||
|
=> value.HasValue && value.Value.TimeOfDay != TimeSpan.Zero;
|
||||||
|
|
||||||
private static string FormatName(string? firstName, string? lastName)
|
private static string FormatName(string? firstName, string? lastName)
|
||||||
=> $"{firstName ?? ""} {lastName ?? ""}".Trim();
|
=> $"{firstName ?? ""} {lastName ?? ""}".Trim();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -651,8 +651,8 @@ namespace SeaHaven.Services.Implementation
|
||||||
var oldEnd = FormatDateTime(workOrder.ScheduledEnd);
|
var oldEnd = FormatDateTime(workOrder.ScheduledEnd);
|
||||||
var dispatchId = ResolveDispatchIdForAudit(dispatch);
|
var dispatchId = ResolveDispatchIdForAudit(dispatch);
|
||||||
|
|
||||||
workOrder.ScheduledStart = WorkOrderBoardApptTimeParser.CombineDateAndTime(apptDate, start);
|
workOrder.ScheduledStart = WorkOrderBoardApptTimeParser.ToScheduledInstant(apptDate, start);
|
||||||
workOrder.ScheduledEnd = WorkOrderBoardApptTimeParser.CombineDateAndTime(apptDate, end);
|
workOrder.ScheduledEnd = WorkOrderBoardApptTimeParser.ToScheduledInstant(apptDate, end);
|
||||||
|
|
||||||
var newStart = FormatDateTime(workOrder.ScheduledStart);
|
var newStart = FormatDateTime(workOrder.ScheduledStart);
|
||||||
var newEnd = FormatDateTime(workOrder.ScheduledEnd);
|
var newEnd = FormatDateTime(workOrder.ScheduledEnd);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using SeaHaven.Services.Helpers;
|
||||||
|
|
||||||
|
namespace SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class WorkOrderBoardFieldMutationsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ApplyApptTime_EmptyValue_ClearsStartAndEnd_KeepsDates()
|
||||||
|
{
|
||||||
|
var appointmentDate = new DateTime(2026, 6, 25);
|
||||||
|
var workOrder = new WorkOrder
|
||||||
|
{
|
||||||
|
ScheduledDate = appointmentDate,
|
||||||
|
ScheduledStart = new DateTime(2026, 6, 25, 9, 0, 0),
|
||||||
|
ScheduledEnd = new DateTime(2026, 6, 25, 11, 0, 0)
|
||||||
|
};
|
||||||
|
var dispatch = new Dispatch { Id = 10, ScheduledDate = appointmentDate };
|
||||||
|
|
||||||
|
WorkOrderBoardFieldMutations.ApplyApptTime(workOrder, dispatch, "");
|
||||||
|
|
||||||
|
Assert.Null(workOrder.ScheduledStart);
|
||||||
|
Assert.Null(workOrder.ScheduledEnd);
|
||||||
|
Assert.Equal(appointmentDate, workOrder.ScheduledDate);
|
||||||
|
Assert.Equal(appointmentDate, dispatch.ScheduledDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyApptTime_StartOnly_LeavesEndNull()
|
||||||
|
{
|
||||||
|
var appointmentDate = new DateTime(2026, 6, 25);
|
||||||
|
var workOrder = new WorkOrder { ScheduledDate = appointmentDate };
|
||||||
|
var dispatch = new Dispatch { Id = 10, ScheduledDate = appointmentDate };
|
||||||
|
|
||||||
|
WorkOrderBoardFieldMutations.ApplyApptTime(workOrder, dispatch, "09:00");
|
||||||
|
|
||||||
|
Assert.Equal(new DateTime(2026, 6, 25, 9, 0, 0), workOrder.ScheduledStart);
|
||||||
|
Assert.Null(workOrder.ScheduledEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyApptTime_Range_SetsStartAndEnd()
|
||||||
|
{
|
||||||
|
var appointmentDate = new DateTime(2026, 6, 25);
|
||||||
|
var workOrder = new WorkOrder { ScheduledDate = appointmentDate };
|
||||||
|
var dispatch = new Dispatch { Id = 10, ScheduledDate = appointmentDate };
|
||||||
|
|
||||||
|
WorkOrderBoardFieldMutations.ApplyApptTime(workOrder, dispatch, "09:00 – 11:00");
|
||||||
|
|
||||||
|
Assert.Equal(new DateTime(2026, 6, 25, 9, 0, 0), workOrder.ScheduledStart);
|
||||||
|
Assert.Equal(new DateTime(2026, 6, 25, 11, 0, 0), workOrder.ScheduledEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -869,4 +869,93 @@ public class WorkOrderBoardServiceTests
|
||||||
null,
|
null,
|
||||||
cts.Token));
|
cts.Token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MapRawRow_DateOnlyDispatch_KeepsApptDate_LeavesApptTimeNull()
|
||||||
|
{
|
||||||
|
var dispatchApptDate = new DateTime(2026, 6, 25);
|
||||||
|
var row = RawRow(
|
||||||
|
scheduledDate: new DateTime(2026, 6, 23),
|
||||||
|
scheduledStart: null,
|
||||||
|
scheduledEnd: null,
|
||||||
|
dispatchApptDate: dispatchApptDate);
|
||||||
|
|
||||||
|
var dto = WorkOrderBoardService.MapRawRow(row, DateTime.UtcNow);
|
||||||
|
|
||||||
|
Assert.Equal(dispatchApptDate, dto.ApptDate);
|
||||||
|
Assert.Null(dto.ApptTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MapRawRow_DispatchWithClock_FallsBackToDispatchTime()
|
||||||
|
{
|
||||||
|
var dispatchApptDate = new DateTime(2026, 6, 25, 9, 0, 0);
|
||||||
|
var row = RawRow(
|
||||||
|
scheduledDate: new DateTime(2026, 6, 23),
|
||||||
|
scheduledStart: null,
|
||||||
|
scheduledEnd: null,
|
||||||
|
dispatchApptDate: dispatchApptDate);
|
||||||
|
|
||||||
|
var dto = WorkOrderBoardService.MapRawRow(row, DateTime.UtcNow);
|
||||||
|
|
||||||
|
Assert.Equal(dispatchApptDate, dto.ApptDate);
|
||||||
|
Assert.Equal("09:00", dto.ApptTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WorkOrderBoardRawRow RawRow(
|
||||||
|
DateTime? scheduledDate,
|
||||||
|
DateTime? scheduledStart,
|
||||||
|
DateTime? scheduledEnd,
|
||||||
|
DateTime? dispatchApptDate)
|
||||||
|
=> new(
|
||||||
|
Id: 1,
|
||||||
|
InternalWONumber: "10000000001",
|
||||||
|
RescheduleCount: 0,
|
||||||
|
CarriedOver: 0,
|
||||||
|
IsAddOn: false,
|
||||||
|
WorkOrderType: null,
|
||||||
|
SiteCode: null,
|
||||||
|
LocationName: null,
|
||||||
|
PocName: null,
|
||||||
|
PocPhone: null,
|
||||||
|
PocNotes: null,
|
||||||
|
LifecycleStatus: LifecycleStatus.Scheduled,
|
||||||
|
LegacyStatus: null,
|
||||||
|
AssignTo: null,
|
||||||
|
DispatcherFirstName: null,
|
||||||
|
DispatcherLastName: null,
|
||||||
|
Initials: null,
|
||||||
|
Color: null,
|
||||||
|
DueDate: null,
|
||||||
|
ScheduledDate: scheduledDate,
|
||||||
|
ScheduledStart: scheduledStart,
|
||||||
|
ScheduledEnd: scheduledEnd,
|
||||||
|
TargetWeek: null,
|
||||||
|
ScheduleWeekOnly: null,
|
||||||
|
VendorId: null,
|
||||||
|
VendorName: null,
|
||||||
|
TechName: null,
|
||||||
|
TechPhone: null,
|
||||||
|
DispatchApptDate: dispatchApptDate,
|
||||||
|
Trade: null,
|
||||||
|
Problem: null,
|
||||||
|
ServiceNotes: null,
|
||||||
|
ExtraServices: null,
|
||||||
|
AdditionalContacts: null,
|
||||||
|
DocStatus: null,
|
||||||
|
CompletedDate: null,
|
||||||
|
FlagColor: null,
|
||||||
|
PrimaryDispatchId: 10,
|
||||||
|
RowVersion: null,
|
||||||
|
DispatchRowVersion: null,
|
||||||
|
PendingUpliftCount: 0,
|
||||||
|
HasUplift: false,
|
||||||
|
PrimaryUpliftStatus: null,
|
||||||
|
PrimaryUpliftAmount: null,
|
||||||
|
PrimaryDispatchStatus: null,
|
||||||
|
AvetaRequired: false,
|
||||||
|
HasAvetaDocument: false,
|
||||||
|
OriginalDate: null,
|
||||||
|
OriginalWeek: null,
|
||||||
|
IsUnscheduled: false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1070,6 +1070,53 @@ public class WorkOrderBoardUpdateServiceTests
|
||||||
Assert.Equal(new DateTime(2026, 6, 25, 10, 0, 0), reloaded.ScheduledEnd);
|
Assert.Equal(new DateTime(2026, 6, 25, 10, 0, 0), reloaded.ScheduledEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_ApptTime_EmptyClearsStartAndEnd_KeepsAppointmentDate()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
var appointmentDate = new DateTime(2026, 6, 25);
|
||||||
|
var dispatch = new Dispatch
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
WorkOrderId = 1,
|
||||||
|
VendorId = 1,
|
||||||
|
ScheduledDate = appointmentDate,
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||||
|
};
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
PrimaryDispatchId = 10,
|
||||||
|
ScheduledDate = appointmentDate,
|
||||||
|
ScheduledStart = new DateTime(2026, 6, 25, 9, 0, 0),
|
||||||
|
ScheduledEnd = new DateTime(2026, 6, 25, 11, 0, 0),
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
};
|
||||||
|
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Vendor A" });
|
||||||
|
context.Dispatches.Add(dispatch);
|
||||||
|
context.workOrders.Add(wo);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||||
|
{
|
||||||
|
Field = WorkOrderBoardFieldNames.ApptTime,
|
||||||
|
Value = "",
|
||||||
|
WorkOrderVersion = ToVersion(wo),
|
||||||
|
DispatchVersion = ToVersion(dispatch),
|
||||||
|
PrimaryDispatchId = 10
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.True(string.IsNullOrEmpty(result.ApptTime));
|
||||||
|
Assert.Equal(appointmentDate, result.ScheduledDate);
|
||||||
|
var reloaded = await context.workOrders.FindAsync(1);
|
||||||
|
Assert.Null(reloaded!.ScheduledStart);
|
||||||
|
Assert.Null(reloaded.ScheduledEnd);
|
||||||
|
Assert.Equal(appointmentDate, reloaded.ScheduledDate);
|
||||||
|
var reloadedDispatch = await context.Dispatches.FindAsync(10);
|
||||||
|
Assert.Equal(appointmentDate, reloadedDispatch!.ScheduledDate);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PatchField_ApptTime_DashPrefixedToken_TreatedAsSingleStart()
|
public async Task PatchField_ApptTime_DashPrefixedToken_TreatedAsSingleStart()
|
||||||
{
|
{
|
||||||
|
|
@ -1106,7 +1153,7 @@ public class WorkOrderBoardUpdateServiceTests
|
||||||
|
|
||||||
var reloaded = await context.workOrders.FindAsync(1);
|
var reloaded = await context.workOrders.FindAsync(1);
|
||||||
Assert.NotNull(reloaded!.ScheduledStart);
|
Assert.NotNull(reloaded!.ScheduledStart);
|
||||||
Assert.Equal(new DateTime(2026, 6, 25), reloaded.ScheduledEnd);
|
Assert.Null(reloaded.ScheduledEnd);
|
||||||
Assert.Equal(new DateTime(2026, 6, 25).Add(TimeSpan.FromDays(30)), reloaded.ScheduledStart);
|
Assert.Equal(new DateTime(2026, 6, 25).Add(TimeSpan.FromDays(30)), reloaded.ScheduledStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue