mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-02 07:23:21 +00:00
* fix(migrations): make Phase6 completion slide-over migration idempotent Use IF EXISTS / IF COL_LENGTH guards and explicit EF migration metadata so partial environments can apply Phase 6 safely. * feat(locations): add sites options endpoint and vendor contactName Expose GET /api/locations/sites for the WO create wizard and include vendor contactName on dropdown responses. * feat(work-orders): add structured service notes POC and vendor fields Persist primary/extra services, service notes, free-text POC, and tech/vendor notes on create/patch/board/detail instead of embedding them in description. * fix(work-orders): make stacked tip build for CI quality gates * fix(work-orders): align stacked services with CI build * fix(work-orders): keep FlagColor out of structured-notes slice Remove FlagColor model/API/tests from this PR so schema and EF model stay aligned with the structured-fields migration. FlagColor remains in the follow-on PR that adds its migration. --------- Co-authored-by: Arthur Bassi <arthur.winiarski.ranger@outlook.com>
87 lines
3 KiB
C#
87 lines
3 KiB
C#
using Data.SeaHavenIndustries;
|
|
using SeaHaven.Services.DTOs;
|
|
|
|
namespace SeaHaven.Services.Helpers
|
|
{
|
|
public static class WorkOrderAuditProjection
|
|
{
|
|
private const string UnassignedLabel = "Unassigned";
|
|
|
|
public static WorkOrderAuditEntryDto ToDto(
|
|
WorkOrderAuditLog log,
|
|
IReadOnlyDictionary<string, string>? assignToDisplayNames = null)
|
|
{
|
|
var oldValue = log.OldValue ?? "";
|
|
var newValue = log.NewValue ?? "";
|
|
|
|
if (IsAssignToField(log.FieldName))
|
|
{
|
|
oldValue = ResolveAssignToDisplay(oldValue, assignToDisplayNames);
|
|
newValue = ResolveAssignToDisplay(newValue, assignToDisplayNames);
|
|
}
|
|
|
|
return new WorkOrderAuditEntryDto
|
|
{
|
|
Type = MapEventType(log.EventType),
|
|
DispatcherId = log.UserId,
|
|
Action = log.Action ?? "",
|
|
FieldName = log.FieldName,
|
|
OldValue = oldValue,
|
|
NewValue = newValue,
|
|
Time = log.CreatedAt.ToString("o"),
|
|
DispatchId = log.DispatchId
|
|
};
|
|
}
|
|
|
|
public static List<WorkOrderAuditEntryDto> ToDtos(
|
|
IEnumerable<WorkOrderAuditLog> logs,
|
|
IReadOnlyDictionary<string, string>? assignToDisplayNames = null)
|
|
=> logs.Select(log => ToDto(log, assignToDisplayNames)).ToList();
|
|
|
|
public static IEnumerable<string> CollectAssignToUserIds(IEnumerable<WorkOrderAuditLog> logs)
|
|
{
|
|
return logs
|
|
.Where(l => IsAssignToField(l.FieldName))
|
|
.SelectMany(l => new[] { l.OldValue, l.NewValue })
|
|
.Where(v => !string.IsNullOrWhiteSpace(v))
|
|
.Select(v => v!)
|
|
.Distinct(StringComparer.Ordinal);
|
|
}
|
|
|
|
internal static bool IsAssignToField(string? fieldName)
|
|
=> string.Equals(fieldName, "AssignTo", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(fieldName, "AssignedTo", StringComparison.OrdinalIgnoreCase);
|
|
|
|
private static string ResolveAssignToDisplay(
|
|
string value,
|
|
IReadOnlyDictionary<string, string>? assignToDisplayNames)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
return UnassignedLabel;
|
|
|
|
if (assignToDisplayNames != null
|
|
&& assignToDisplayNames.TryGetValue(value, out var name)
|
|
&& !string.IsNullOrWhiteSpace(name))
|
|
{
|
|
return name;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private static string MapEventType(string? eventType)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(eventType))
|
|
return "manual";
|
|
|
|
return eventType.ToLowerInvariant() switch
|
|
{
|
|
"manual" => "manual",
|
|
"system" => "system",
|
|
"sync" => "sync",
|
|
"vendor" => "vendor",
|
|
_ => eventType.ToLowerInvariant()
|
|
};
|
|
}
|
|
}
|
|
}
|