using Data.SeaHavenIndustries; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Interfaces; namespace SeaHaven.DataServices.Implementation { public sealed class WorkOrderWebhookDataService : IWorkOrderWebhookDataService { private readonly ApplicationDbContext _context; public WorkOrderWebhookDataService(ApplicationDbContext context) { _context = context; } public async Task ApplyAsync( WorkOrderWebhookMutation mutation, CancellationToken cancellationToken) { var prior = await _context.WorkOrderWebhookDeliveries .AsNoTracking() .SingleOrDefaultAsync(d => d.DeliveryId == mutation.DeliveryId, cancellationToken); if (prior != null) return ExistingDeliveryResult(prior, mutation.BodySha256); var workOrder = await _context.workOrders .SingleOrDefaultAsync( w => w.ExternalWorkOrderId == mutation.ExternalWorkOrderId, cancellationToken); if (workOrder == null) { var nextInternalNumber = Math.Max( await _context.workOrders.MaxAsync( w => (int?)w.Id, cancellationToken) ?? 0, 0) + 1; workOrder = new WorkOrder { ExternalWorkOrderId = mutation.ExternalWorkOrderId, WorkerOrderNumber = mutation.WorkerOrderNumber, InternalWONumber = nextInternalNumber.ToString("D11"), WorkerOrderTitle = mutation.Title ?? mutation.Description ?? $"Imported work order {mutation.ExternalWorkOrderId}", Description = mutation.Description, Source = mutation.Source, istemplate = false, CreatedDate = mutation.CreatedAt ?? mutation.ProcessedAt.UtcDateTime }; _context.workOrders.Add(workOrder); } var staleState = mutation.IsStateEvent && workOrder.ExternalLastOccurredAt.HasValue && mutation.OccurredAt < workOrder.ExternalLastOccurredAt.Value; if (mutation.IsStateEvent && !staleState) { var locationKey = mutation.SiteCode ?? mutation.Building; if (!string.IsNullOrWhiteSpace(locationKey)) { var location = await _context.Locations .FirstOrDefaultAsync( l => l.Name == locationKey || l.Title == locationKey, cancellationToken); if (location == null) { location = new Locations { Name = locationKey, Title = mutation.Building, Address1 = mutation.Address, Status = "Active" }; _context.Locations.Add(location); } workOrder.Locations = location; } workOrder.WorkerOrderNumber = mutation.WorkerOrderNumber; workOrder.WorkerOrderTitle = mutation.Title ?? mutation.Description; workOrder.Description = mutation.Description; workOrder.Status = mutation.IsCancelled ? "Cancelled" : mutation.Status; workOrder.Severity = mutation.Severity; workOrder.Priority = mutation.Priority; workOrder.Customer = mutation.Customer; workOrder.SiteCode = mutation.SiteCode; workOrder.Building = mutation.Building; workOrder.DueDate = mutation.DueDate; workOrder.DateReported = mutation.DateReported; workOrder.ScheduledStart = mutation.ScheduledStart; workOrder.Source = mutation.Source; workOrder.istemplate = false; workOrder.ExternalLastOccurredAt = mutation.OccurredAt; } if (mutation.CommentId != null) { var commentExists = await _context.Comments .AnyAsync(c => c.ExternalCommentId == mutation.CommentId, cancellationToken); if (!commentExists) { _context.Comments.Add(new Comments { ExternalCommentId = mutation.CommentId, Commenttext = mutation.CommentText, Commenter = mutation.Commenter, CommentType = mutation.CommentType, RecordType = "WorkOrder", WorkOrder = workOrder, CreatedDate = mutation.OccurredAt.UtcDateTime }); } } _context.WorkOrderWebhookDeliveries.Add(new WorkOrderWebhookDelivery { DeliveryId = mutation.DeliveryId, EventType = mutation.EventType, OccurredAt = mutation.OccurredAt, ProcessedAt = mutation.ProcessedAt, BodySha256 = mutation.BodySha256 }); try { await _context.SaveChangesAsync(cancellationToken); return new WorkOrderWebhookPersistenceResult( WorkOrderWebhookPersistenceStatus.Applied, staleState); } catch (DbUpdateException) { _context.ChangeTracker.Clear(); var concurrent = await _context.WorkOrderWebhookDeliveries .AsNoTracking() .SingleOrDefaultAsync(d => d.DeliveryId == mutation.DeliveryId, cancellationToken); if (concurrent == null) throw; return ExistingDeliveryResult(concurrent, mutation.BodySha256); } } private static WorkOrderWebhookPersistenceResult ExistingDeliveryResult( WorkOrderWebhookDelivery delivery, string bodySha256) { return new WorkOrderWebhookPersistenceResult( string.Equals(delivery.BodySha256, bodySha256, StringComparison.OrdinalIgnoreCase) ? WorkOrderWebhookPersistenceStatus.Duplicate : WorkOrderWebhookPersistenceStatus.HashConflict); } } }