mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 14:13:12 +00:00
Expose workorders/{id}/uplifts list/create/cancel/revoke for the SH-196 dialog, aggregate upliftSummary on board rows, and add service/controller regression tests.
95 lines
3.9 KiB
C#
95 lines
3.9 KiB
C#
namespace SeaHaven.Services.Implementation
|
|
{
|
|
/// <summary>
|
|
/// Server-owned uplift state machine (SH-101). Canonical states are
|
|
/// Pending, Approved, Rejected, ChangesRequested, Withdrawn, Expired.
|
|
/// Legacy stored values "Denied" (-> Rejected) and "Cancelled" (-> Withdrawn)
|
|
/// are read as their canonical equivalents but never (re)written.
|
|
/// </summary>
|
|
public static class UpliftStatus
|
|
{
|
|
public const string Pending = "Pending";
|
|
public const string Approved = "Approved";
|
|
public const string Rejected = "Rejected";
|
|
public const string ChangesRequested = "ChangesRequested";
|
|
public const string Withdrawn = "Withdrawn";
|
|
public const string Expired = "Expired";
|
|
public const string NoApprovalRequired = "NoApprovalRequired";
|
|
public const string Revoked = "Revoked";
|
|
|
|
// Legacy aliases retained only for reads; never written by new code.
|
|
public const string LegacyDenied = "Denied";
|
|
public const string LegacyCancelled = "Cancelled";
|
|
|
|
private static readonly HashSet<string> TerminalStates =
|
|
new(StringComparer.Ordinal) { Approved, Rejected, Withdrawn, Expired, Revoked, NoApprovalRequired };
|
|
|
|
private static readonly HashSet<string> ActiveStates =
|
|
new(StringComparer.Ordinal) { Pending, ChangesRequested };
|
|
|
|
/// <summary>
|
|
/// Maps a stored status to its canonical DTO value. Legacy "Denied" is
|
|
/// read as "Rejected" and "Cancelled" as "Withdrawn".
|
|
/// </summary>
|
|
public static string ToCanonical(string? status) => status switch
|
|
{
|
|
LegacyDenied => Rejected,
|
|
LegacyCancelled => Withdrawn,
|
|
_ => status ?? Pending
|
|
};
|
|
|
|
/// <summary>True for terminal (immutable) states, including legacy aliases.</summary>
|
|
public static bool IsTerminal(string? status) =>
|
|
TerminalStates.Contains(ToCanonical(status));
|
|
|
|
/// <summary>True for actionable states that block a new active request.</summary>
|
|
public static bool IsActive(string? status) =>
|
|
ActiveStates.Contains(ToCanonical(status));
|
|
|
|
/// <summary>
|
|
/// Validates a server-owned transition. Only legal transitions return true.
|
|
/// Terminal states (incl. legacy aliases) never transition out.
|
|
/// </summary>
|
|
public static bool CanTransition(string? from, string to)
|
|
{
|
|
var canonicalFrom = ToCanonical(from);
|
|
|
|
if (TerminalStates.Contains(canonicalFrom))
|
|
return false;
|
|
|
|
return canonicalFrom switch
|
|
{
|
|
Pending => to is Approved or Rejected or ChangesRequested or Withdrawn or Expired,
|
|
ChangesRequested => to is Pending or Withdrawn or Expired,
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
/// <summary>Snapshot-readable active states, used by data-service filters.</summary>
|
|
public static IReadOnlyCollection<string> ActiveStatuses => ActiveStates;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notification pipeline status (separate from the workflow <see cref="UpliftStatus"/>).
|
|
/// A notification failure never destroys an actionable request.
|
|
/// </summary>
|
|
public static class UpliftNotificationStatus
|
|
{
|
|
public const string Pending = "Pending";
|
|
public const string Sent = "Sent";
|
|
public const string Error = "Error";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vendor completion document purposes (SH-101). Completion is the legacy/default
|
|
/// behavior; UpliftEvidence is immutable supporting evidence for an uplift request.
|
|
/// </summary>
|
|
public static class VendorDocumentPurpose
|
|
{
|
|
public const string Completion = "Completion";
|
|
public const string UpliftEvidence = "UpliftEvidence";
|
|
|
|
public static bool IsValid(string? purpose) =>
|
|
purpose is Completion or UpliftEvidence;
|
|
}
|
|
}
|