using Data.SeaHavenIndustries; using Microsoft.EntityFrameworkCore; using SeaHaven.Services.DTOs; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; namespace SeaHaven.Services.Implementation { public class WorkOrderIngestService : IWorkOrderIngestService { private readonly ApplicationDbContext _db; private readonly ISyncFieldMergePolicy _mergePolicy; private readonly IWorkOrderFieldLockService _fieldLocks; private readonly IWorkOrderAuditService _audit; public WorkOrderIngestService( ApplicationDbContext db, ISyncFieldMergePolicy mergePolicy, IWorkOrderFieldLockService fieldLocks, IWorkOrderAuditService audit) { _db = db; _mergePolicy = mergePolicy; _fieldLocks = fieldLocks; _audit = audit; } public async Task UpsertBatchAsync( IReadOnlyList items, CancellationToken cancellationToken = default) { var result = new WorkOrderIngestBatchResultDto(); if (items.Count == 0) return result; var nextInternal = await GetNextInternalWoNumberAsync(cancellationToken); foreach (var item in items) { if (string.IsNullOrWhiteSpace(item.ExternalWorkOrderId)) continue; var upsert = await UpsertOneAsync(item, ref nextInternal, cancellationToken); result.Results.Add(upsert); if (upsert.Created) result.Created++; else result.Updated++; } await _db.SaveChangesAsync(cancellationToken); return result; } private async Task UpsertOneAsync( WorkOrderIngestPayloadDto item, ref int nextInternal, CancellationToken cancellationToken) { var existing = await _db.workOrders .FirstOrDefaultAsync(w => w.ExternalWorkOrderId == item.ExternalWorkOrderId, cancellationToken); var locationId = await ResolveLocationIdAsync( item.SiteCode, item.Building, item.Address, cancellationToken); if (existing == null) { var wo = new WorkOrder { InternalWONumber = (nextInternal++).ToString("D8"), ExternalWorkOrderId = item.ExternalWorkOrderId, WorkerOrderNumber = item.ExternalWorkOrderId, WorkerOrderTitle = item.Description, Description = item.Description, Status = WorkOrderIngestFieldMapper.MapStatus(item.WoStatus) ?? "Open", Priority = WorkOrderIngestFieldMapper.MapSeverityToPriority(item.Severity), Severity = item.Severity, Customer = item.Customer, SiteCode = item.SiteCode, Building = item.Building, LocationId = locationId, DueDate = item.DueDate, DateReported = item.DateReported, ScheduledStart = item.ScheduledStart, SourceEmailS3Key = item.SourceEmailS3Key, CreatedDate = item.CreatedAt ?? DateTime.UtcNow, istemplate = false }; _db.workOrders.Add(wo); await _db.SaveChangesAsync(cancellationToken); return new WorkOrderIngestItemResultDto { ExternalWorkOrderId = item.ExternalWorkOrderId, WorkOrderId = wo.Id, Created = true }; } var syncContext = new WorkOrderSyncContext { WorkOrder = existing, FieldLocks = _fieldLocks, Audit = _audit }; if (item.Description != null) { await _mergePolicy.TryApplyAsync(syncContext, "Description", item.Description); await _mergePolicy.TryApplyAsync(syncContext, "WorkerOrderTitle", item.Description); } var mappedStatus = WorkOrderIngestFieldMapper.MapStatus(item.WoStatus); if (mappedStatus != null) await _mergePolicy.TryApplyAsync(syncContext, "Status", mappedStatus); var priority = WorkOrderIngestFieldMapper.MapSeverityToPriority(item.Severity); if (priority != null) await _mergePolicy.TryApplyAsync(syncContext, "Priority", priority); if (item.Severity != null) await _mergePolicy.TryApplyAsync(syncContext, "Severity", item.Severity); if (item.SiteCode != null) await _mergePolicy.TryApplyAsync(syncContext, "SiteCode", item.SiteCode); if (item.Building != null) await _mergePolicy.TryApplyAsync(syncContext, "Building", item.Building); existing.LocationId = locationId ?? existing.LocationId; if (item.DueDate.HasValue) await _mergePolicy.TryApplyAsync(syncContext, "DueDate", item.DueDate.Value); existing.DateReported = item.DateReported ?? existing.DateReported; existing.ScheduledStart = item.ScheduledStart ?? existing.ScheduledStart; existing.SourceEmailS3Key = item.SourceEmailS3Key ?? existing.SourceEmailS3Key; return new WorkOrderIngestItemResultDto { ExternalWorkOrderId = item.ExternalWorkOrderId, WorkOrderId = existing.Id, Created = false }; } private async Task GetNextInternalWoNumberAsync(CancellationToken cancellationToken) { var lastInternalWo = await _db.workOrders .Where(w => w.InternalWONumber != null && w.InternalWONumber != "") .OrderByDescending(w => w.InternalWONumber) .Select(w => w.InternalWONumber) .FirstOrDefaultAsync(cancellationToken); var next = 10000001; if (lastInternalWo != null && int.TryParse(lastInternalWo, out var parsed)) next = parsed + 1; return next; } private async Task ResolveLocationIdAsync( string? siteCode, string? building, string? address, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(siteCode) && string.IsNullOrWhiteSpace(building)) return null; var matchCode = siteCode ?? building; var existing = await _db.Locations .FirstOrDefaultAsync(l => l.Name == matchCode || l.Title == matchCode, cancellationToken); if (existing != null) return existing.Id; var location = new Locations { Name = matchCode, Title = building, Address1 = address, Status = "Active" }; _db.Locations.Add(location); await _db.SaveChangesAsync(cancellationToken); return location.Id; } } }