mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
Align board apptTime field locks with SyncFieldMergePolicy field names so sync cannot overwrite a manual appointment edit. Also add shared LifecycleStatusSets so the Phase 2 branch compiles.
224 lines
9.3 KiB
C#
224 lines
9.3 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using SeaHaven.Services.Constants;
|
|
using SeaHaven.Services.Interfaces;
|
|
|
|
namespace SeaHaven.Services.Implementation
|
|
{
|
|
public class SyncFieldMergePolicy : ISyncFieldMergePolicy
|
|
{
|
|
private static readonly HashSet<string> ShocOwnedFieldSet = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
WorkOrderFieldNames.LifecycleStatus,
|
|
WorkOrderFieldNames.AssignTo,
|
|
WorkOrderFieldNames.ScheduledDate,
|
|
WorkOrderFieldNames.ScheduledStart,
|
|
WorkOrderFieldNames.ScheduledEnd,
|
|
WorkOrderFieldNames.TargetWeek,
|
|
WorkOrderFieldNames.ScheduleWeekOnly,
|
|
WorkOrderFieldNames.DueDate,
|
|
WorkOrderFieldNames.Description,
|
|
WorkOrderFieldNames.WorkerOrderTitle,
|
|
WorkOrderFieldNames.SiteCode,
|
|
WorkOrderFieldNames.Building,
|
|
WorkOrderFieldNames.LocationId,
|
|
WorkOrderFieldNames.WorkOrderType,
|
|
WorkOrderFieldNames.Service,
|
|
WorkOrderFieldNames.DocStatus,
|
|
WorkOrderFieldNames.Trade,
|
|
WorkOrderFieldNames.Problem,
|
|
WorkOrderFieldNames.Priority,
|
|
WorkOrderFieldNames.InternalWONumber,
|
|
WorkOrderFieldNames.Status,
|
|
};
|
|
|
|
private readonly IWorkOrderFieldLockService _fieldLocks;
|
|
|
|
public SyncFieldMergePolicy(IWorkOrderFieldLockService fieldLocks)
|
|
{
|
|
_fieldLocks = fieldLocks;
|
|
}
|
|
|
|
public IReadOnlySet<string> ShocOwnedFields => ShocOwnedFieldSet;
|
|
|
|
public async Task<bool> CanSyncOverwriteAsync(int workOrderId, string fieldName)
|
|
=> !await _fieldLocks.IsLockedAsync(workOrderId, fieldName);
|
|
|
|
public async Task<bool> TryApplyAsync(WorkOrderSyncContext context, string fieldName, object? incomingValue)
|
|
{
|
|
if (incomingValue == null) return false;
|
|
|
|
if (await context.FieldLocks.IsLockedAsync(context.WorkOrder.Id, fieldName))
|
|
{
|
|
var current = GetFieldValue(context.WorkOrder, fieldName);
|
|
await context.Audit.LogSyncRejectedAsync(
|
|
context.WorkOrder.Id,
|
|
fieldName,
|
|
current,
|
|
incomingValue.ToString());
|
|
return false;
|
|
}
|
|
|
|
return TryApplyField(context.WorkOrder, fieldName, incomingValue);
|
|
}
|
|
|
|
private static string? GetFieldValue(WorkOrder wo, string fieldName) => fieldName switch
|
|
{
|
|
WorkOrderFieldNames.Description => wo.Description,
|
|
WorkOrderFieldNames.WorkerOrderTitle => wo.WorkerOrderTitle,
|
|
WorkOrderFieldNames.Status => wo.Status,
|
|
WorkOrderFieldNames.DueDate => wo.DueDate?.ToString("O"),
|
|
WorkOrderFieldNames.ScheduledDate => wo.ScheduledDate?.ToString("O"),
|
|
WorkOrderFieldNames.ScheduledStart => wo.ScheduledStart?.ToString("O"),
|
|
WorkOrderFieldNames.ScheduledEnd => wo.ScheduledEnd?.ToString("O"),
|
|
WorkOrderFieldNames.TargetWeek => wo.TargetWeek?.ToString("O"),
|
|
WorkOrderFieldNames.ScheduleWeekOnly => wo.ScheduleWeekOnly?.ToString(),
|
|
WorkOrderFieldNames.SiteCode => wo.SiteCode,
|
|
WorkOrderFieldNames.Building => wo.Building,
|
|
WorkOrderFieldNames.Priority => wo.Priority,
|
|
WorkOrderFieldNames.AssignTo => wo.AssignTo,
|
|
WorkOrderFieldNames.LocationId => wo.LocationId?.ToString(),
|
|
WorkOrderFieldNames.LifecycleStatus => wo.LifecycleStatus?.ToString(),
|
|
WorkOrderFieldNames.WorkOrderType => wo.WorkOrderType?.ToString(),
|
|
WorkOrderFieldNames.Service => wo.Service,
|
|
WorkOrderFieldNames.DocStatus => wo.DocStatus?.ToString(),
|
|
WorkOrderFieldNames.Trade => wo.Trade,
|
|
WorkOrderFieldNames.Problem => wo.Problem,
|
|
WorkOrderFieldNames.InternalWONumber => wo.InternalWONumber,
|
|
_ => null
|
|
};
|
|
|
|
private static bool TryApplyField(WorkOrder wo, string fieldName, object incomingValue)
|
|
{
|
|
switch (fieldName)
|
|
{
|
|
case WorkOrderFieldNames.Description:
|
|
wo.Description = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.WorkerOrderTitle:
|
|
wo.WorkerOrderTitle = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.Status:
|
|
wo.Status = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.DueDate when TryParseDateTime(incomingValue, out var dueDate):
|
|
wo.DueDate = dueDate;
|
|
return true;
|
|
case WorkOrderFieldNames.ScheduledDate when TryParseDateTime(incomingValue, out var scheduledDate):
|
|
wo.ScheduledDate = scheduledDate;
|
|
return true;
|
|
case WorkOrderFieldNames.ScheduledStart when TryParseDateTime(incomingValue, out var scheduledStart):
|
|
wo.ScheduledStart = scheduledStart;
|
|
return true;
|
|
case WorkOrderFieldNames.ScheduledEnd when TryParseDateTime(incomingValue, out var scheduledEnd):
|
|
wo.ScheduledEnd = scheduledEnd;
|
|
return true;
|
|
case WorkOrderFieldNames.TargetWeek when TryParseDateOnly(incomingValue, out var targetWeek):
|
|
wo.TargetWeek = targetWeek;
|
|
return true;
|
|
case WorkOrderFieldNames.ScheduleWeekOnly when TryParseBool(incomingValue, out var scheduleWeekOnly):
|
|
wo.ScheduleWeekOnly = scheduleWeekOnly;
|
|
return true;
|
|
case WorkOrderFieldNames.SiteCode:
|
|
wo.SiteCode = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.Building:
|
|
wo.Building = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.Priority:
|
|
wo.Priority = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.AssignTo:
|
|
wo.AssignTo = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.LocationId when TryParseInt(incomingValue, out var locationId):
|
|
wo.LocationId = locationId;
|
|
return true;
|
|
case WorkOrderFieldNames.LifecycleStatus when TryParseEnum(incomingValue, out LifecycleStatus lifecycleStatus):
|
|
wo.LifecycleStatus = lifecycleStatus;
|
|
return true;
|
|
case WorkOrderFieldNames.WorkOrderType when TryParseEnum(incomingValue, out WorkOrderType workOrderType):
|
|
wo.WorkOrderType = workOrderType;
|
|
return true;
|
|
case WorkOrderFieldNames.Service:
|
|
wo.Service = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.DocStatus when TryParseEnum(incomingValue, out DocStatus docStatus):
|
|
wo.DocStatus = docStatus;
|
|
return true;
|
|
case WorkOrderFieldNames.Trade:
|
|
wo.Trade = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.Problem:
|
|
wo.Problem = incomingValue.ToString();
|
|
return true;
|
|
case WorkOrderFieldNames.InternalWONumber:
|
|
wo.InternalWONumber = incomingValue.ToString();
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static bool TryParseDateTime(object value, out DateTime result)
|
|
{
|
|
if (value is DateTime dt)
|
|
{
|
|
result = dt;
|
|
return true;
|
|
}
|
|
|
|
return DateTime.TryParse(value.ToString(), out result);
|
|
}
|
|
|
|
private static bool TryParseDateOnly(object value, out DateOnly result)
|
|
{
|
|
if (value is DateOnly dateOnly)
|
|
{
|
|
result = dateOnly;
|
|
return true;
|
|
}
|
|
|
|
if (value is DateTime dt)
|
|
{
|
|
result = DateOnly.FromDateTime(dt);
|
|
return true;
|
|
}
|
|
|
|
return DateOnly.TryParse(value.ToString(), out result);
|
|
}
|
|
|
|
private static bool TryParseBool(object value, out bool result)
|
|
{
|
|
if (value is bool b)
|
|
{
|
|
result = b;
|
|
return true;
|
|
}
|
|
|
|
return bool.TryParse(value.ToString(), out result);
|
|
}
|
|
|
|
private static bool TryParseInt(object value, out int result)
|
|
{
|
|
if (value is int i)
|
|
{
|
|
result = i;
|
|
return true;
|
|
}
|
|
|
|
return int.TryParse(value.ToString(), out result);
|
|
}
|
|
|
|
private static bool TryParseEnum<TEnum>(object value, out TEnum result) where TEnum : struct, Enum
|
|
{
|
|
if (value is TEnum enumValue)
|
|
{
|
|
result = enumValue;
|
|
return true;
|
|
}
|
|
|
|
return Enum.TryParse(value.ToString(), true, out result);
|
|
}
|
|
}
|
|
}
|