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"));
+ }
}