diff --git a/SeaHaven.Services/DTOs/WorkOrderBoardRequestDTOs.cs b/SeaHaven.Services/DTOs/WorkOrderBoardRequestDTOs.cs index 593415e..2f66e06 100644 --- a/SeaHaven.Services/DTOs/WorkOrderBoardRequestDTOs.cs +++ b/SeaHaven.Services/DTOs/WorkOrderBoardRequestDTOs.cs @@ -41,6 +41,8 @@ namespace SeaHaven.Services.DTOs public DateOnly? TargetWeek { get; set; } public bool? ScheduleWeekOnly { get; set; } public DocStatus? DocStatus { get; set; } + /// Optional create-time lifecycle. When omitted, server defaults to Incomplete. + public LifecycleStatus? LifecycleStatus { get; set; } public int? VendorId { get; set; } public DateTime? ApptDate { get; set; } public string? ApptTime { get; set; } diff --git a/SeaHaven.Services/Helpers/WorkOrderBoardCreateLifecycleRules.cs b/SeaHaven.Services/Helpers/WorkOrderBoardCreateLifecycleRules.cs new file mode 100644 index 0000000..0f06f3a --- /dev/null +++ b/SeaHaven.Services/Helpers/WorkOrderBoardCreateLifecycleRules.cs @@ -0,0 +1,47 @@ +using Data.SeaHavenIndustries; +using Data.SeaHavenIndustries.Enums; +using SeaHaven.Services.Exceptions; + +namespace SeaHaven.Services.Helpers +{ + public static class WorkOrderBoardCreateLifecycleRules + { + private static readonly HashSet AllowedCreateStatuses = + [ + LifecycleStatus.Incomplete, + LifecycleStatus.Pending, + LifecycleStatus.Scheduled, + ]; + + public static void ApplyRequestedOrInitialLifecycleStatus( + WorkOrder workOrder, + LifecycleStatus? requested, + DateTime? scheduledDate, + bool? scheduleWeekOnly) + { + if (!requested.HasValue) + { + WorkOrderBoardFieldMutations.SetInitialLifecycleStatus(workOrder); + return; + } + + if (!AllowedCreateStatuses.Contains(requested.Value)) + { + throw new WorkOrderBoardValidationException( + "InvalidLifecycleStatus", + "lifecycleStatus must be Incomplete, Pending, or Scheduled at create."); + } + + if (requested == LifecycleStatus.Scheduled + && (!scheduledDate.HasValue || scheduleWeekOnly == true)) + { + throw new WorkOrderBoardValidationException( + "ScheduledRequiresDate", + "Scheduled requires a concrete scheduledDate when scheduleWeekOnly is not true."); + } + + workOrder.LifecycleStatus = requested.Value; + workOrder.Status = WorkOrderDerivedFields.GetLifecycleStatusLabel(requested.Value); + } + } +} diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardCreateService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardCreateService.cs index 37adcc0..536054f 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardCreateService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardCreateService.cs @@ -108,7 +108,11 @@ namespace SeaHaven.Services.Implementation workOrder.VendorNotes = vendorNotes; } - WorkOrderBoardFieldMutations.SetInitialLifecycleStatus(workOrder); + WorkOrderBoardCreateLifecycleRules.ApplyRequestedOrInitialLifecycleStatus( + workOrder, + request.LifecycleStatus, + request.ScheduledDate, + request.ScheduleWeekOnly); workOrder.IsAddOn = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation( workOrder.CreatedDate!.Value, @@ -121,7 +125,7 @@ namespace SeaHaven.Services.Implementation WorkOrderBoardFieldMutations.BoardFieldChange.Changed("InternalWONumber", "", woNumber), WorkOrderBoardFieldMutations.BoardFieldChange.Changed("WorkOrderType", "", request.WorkOrderType.ToString()), WorkOrderBoardFieldMutations.BoardFieldChange.Changed("SiteCode", "", siteCode), - WorkOrderBoardFieldMutations.BoardFieldChange.Changed("LifecycleStatus", "", LifecycleStatus.Incomplete.ToString()) + WorkOrderBoardFieldMutations.BoardFieldChange.Changed("LifecycleStatus", "", workOrder.LifecycleStatus?.ToString() ?? LifecycleStatus.Incomplete.ToString()) }; if (workOrder.Description != null) diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardCreateServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardCreateServiceTests.cs index ef58783..ff2f3d6 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardCreateServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardCreateServiceTests.cs @@ -654,4 +654,54 @@ public class WorkOrderBoardCreateServiceTests Assert.Equal("+1 555-0101", result.Info.AdditionalContacts[0].Phone); Assert.Equal("Security Desk", result.Info.AdditionalContacts[1].Name); } + + [Fact] + public async Task Create_WithPendingLifecycle_PersistsPending() + { + var (_, service) = CreateSut(); + + var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto + { + WorkOrderType = WorkOrderType.PM, + SiteCode = "BK5", + LocationId = 1, + LifecycleStatus = LifecycleStatus.Pending, + }, WorkOrderAccountTestHelpers.AccountUser(), "actor-1"); + + Assert.Equal(LifecycleStatus.Pending, result.LifecycleStatus); + } + + [Fact] + public async Task Create_WithScheduledLifecycleAndDate_PersistsWithoutAssignee() + { + var (_, service) = CreateSut(); + + var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto + { + WorkOrderType = WorkOrderType.PM, + SiteCode = "BK5", + LocationId = 1, + LifecycleStatus = LifecycleStatus.Scheduled, + ScheduledDate = new DateTime(2026, 7, 15), + ScheduleWeekOnly = false, + }, WorkOrderAccountTestHelpers.AccountUser(), "actor-1"); + + Assert.Equal(LifecycleStatus.Scheduled, result.LifecycleStatus); + Assert.Equal(new DateTime(2026, 7, 15), result.ScheduledDate?.Date); + } + + [Fact] + public async Task Create_WithScheduledLifecycleWithoutDate_Throws() + { + var (_, service) = CreateSut(); + + await Assert.ThrowsAsync(() => + service.CreateAsync(new WorkOrderBoardCreateRequestDto + { + WorkOrderType = WorkOrderType.PM, + SiteCode = "BK5", + LocationId = 1, + LifecycleStatus = LifecycleStatus.Scheduled, + }, WorkOrderAccountTestHelpers.AccountUser(), "actor-1")); + } }