using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.Services.Constants; using SeaHaven.Services.Interfaces; namespace SeaHaven.Services.Implementation { public class WorkOrderAuditService : IWorkOrderAuditService { private readonly ApplicationDbContext _context; private readonly IWorkOrderFieldLockService _fieldLocks; public WorkOrderAuditService(ApplicationDbContext context, IWorkOrderFieldLockService fieldLocks) { _context = context; _fieldLocks = fieldLocks; } public Task LogStatusChangedAsync(int workOrderId, string? oldStatus, string? newStatus, string? actorId) => LogAsync(workOrderId, AuditActionType.StatusChanged, AuditEventType.Manual, AuditActorType.Dispatcher, WorkOrderFieldNames.LifecycleStatus, oldStatus, newStatus, actorId); public Task LogAssignmentChangedAsync(int workOrderId, string? oldValue, string? newValue, string? actorId) => LogAsync(workOrderId, AuditActionType.AssignmentChanged, AuditEventType.Manual, AuditActorType.Dispatcher, WorkOrderFieldNames.AssignTo, oldValue, newValue, actorId); public Task LogSyncRejectedAsync(int workOrderId, string fieldName, string? oldValue, string? newValue) => LogAsync(workOrderId, AuditActionType.SyncRejected, AuditEventType.Sync, AuditActorType.Sync, fieldName, oldValue, newValue); public async Task LogAsync( int workOrderId, AuditActionType action, AuditEventType eventType, AuditActorType actorType, string fieldName, string? oldValue, string? newValue, string? actorId = null, int? dispatchId = null, string? correlationId = null) { _context.WorkOrderAuditLogs.Add(new WorkOrderAuditLog { WorkOrderId = workOrderId, UserId = actorId, FieldName = fieldName, OldValue = oldValue ?? "", NewValue = newValue ?? "", Action = action.ToString(), EventType = eventType.ToString(), ActorType = actorType.ToString(), DispatchId = dispatchId, CorrelationId = correlationId, CreatedAt = DateTime.UtcNow }); if (eventType == AuditEventType.Manual && (action == AuditActionType.FieldChanged || action == AuditActionType.StatusChanged || action == AuditActionType.AssignmentChanged)) { await _fieldLocks.LockFieldAsync(workOrderId, fieldName, actorId); } await SaveChangesIdempotentAsync(); } private async Task SaveChangesIdempotentAsync() { try { await _context.SaveChangesAsync(); } catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex)) { foreach (var entry in ex.Entries.Where(e => e.Entity is WorkOrderFieldLock)) entry.State = EntityState.Detached; await _context.SaveChangesAsync(); } } private static bool IsUniqueConstraintViolation(DbUpdateException ex) { var message = ex.InnerException?.Message ?? ex.Message; return message.Contains("duplicate", StringComparison.OrdinalIgnoreCase) || message.Contains("UNIQUE", StringComparison.OrdinalIgnoreCase); } } }