using System.Security.Claims; using SeaHaven.DataServices.Interfaces; using SeaHaven.Services.DTOs; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; namespace SeaHaven.Services.Implementation { public class WorkOrderDetailService : IWorkOrderDetailService { private readonly IWorkOrderBoardService _boardService; private readonly IWorkOrderDetailDataService _detailData; private readonly ICompletionDocTemplateDataService _templateData; private readonly IUserDataService _userDataService; private readonly IWorkOrderAccountResolver _accountResolver; public WorkOrderDetailService( IWorkOrderBoardService boardService, IWorkOrderDetailDataService detailData, ICompletionDocTemplateDataService templateData, IUserDataService userDataService, IWorkOrderAccountResolver accountResolver) { _boardService = boardService; _detailData = detailData; _templateData = templateData; _userDataService = userDataService; _accountResolver = accountResolver; } public async Task GetDetailAsync(int workOrderId, ClaimsPrincipal user) { var accountId = _accountResolver.ResolveAccountFilter(user); if (!await _detailData.ExistsAsync(workOrderId, CancellationToken.None, accountId)) return null; var boardRow = await _boardService.GetBoardRowAsync(workOrderId, user); if (boardRow == null) return null; var extended = await _detailData.GetExtendedFieldsAsync(workOrderId); var comments = await _detailData.GetCommentsAsync(workOrderId); var audit = await _detailData.GetAuditLogsAsync(workOrderId, 50); var attachments = await _detailData.GetAttachmentsAsync(workOrderId); var signoffs = await _detailData.GetDispatchSignoffsAsync(workOrderId); var workOrder = await _detailData.GetWorkOrderForMediaAsync( workOrderId, CancellationToken.None, accountId); var template = await _templateData.ResolveForWorkOrderAsync( extended?.Trade ?? boardRow.Pm, boardRow.WorkOrderType); var authorIds = WorkOrderCommentProjection.CollectAuthorUserIds(comments); var authorNames = await _userDataService.GetDisplayNamesByIdsAsync(authorIds); return new WorkOrderDetailDto { Info = MapInfo(boardRow, extended), Completion = MapCompletion(extended, boardRow.DocStatus, template, signoffs), Comments = WorkOrderCommentProjection.ToDtos(comments, authorNames), Audit = await MapAuditAsync(audit), Media = workOrder == null ? new List() : WorkOrderMediaProjection.ProjectAll(workOrder, attachments) }; } public async Task?> GetAuditAsync( int workOrderId, ClaimsPrincipal user, int limit = 50) { var accountId = _accountResolver.ResolveAccountFilter(user); if (!await _detailData.ExistsAsync(workOrderId, CancellationToken.None, accountId)) return null; var logs = await _detailData.GetAuditLogsAsync(workOrderId, limit); return await MapAuditAsync(logs); } private async Task> MapAuditAsync(IReadOnlyList logs) { var userIds = WorkOrderAuditProjection.CollectAssignToUserIds(logs); var displayNames = await _userDataService.GetDisplayNamesByIdsAsync(userIds); return WorkOrderAuditProjection.ToDtos(logs, displayNames); } private static WorkOrderDetailInfoDto MapInfo( WorkOrderBoardRowDto row, WorkOrderDetailExtendedFields? extended) { return new WorkOrderDetailInfoDto { Id = row.Id, WoNumber = row.WoNumber, RescheduleCount = row.RescheduleCount, CarriedOver = row.CarriedOver, IsAddOn = row.IsAddOn, AvetaRequired = row.AvetaRequired, HasAvetaDocument = row.HasAvetaDocument, WorkOrderType = row.WorkOrderType, IsPastDue = row.IsPastDue, SiteCode = row.SiteCode, LocationName = row.LocationName, PocName = row.PocName, PocPhone = row.PocPhone, PocNotes = row.PocNotes, AdditionalContacts = row.AdditionalContacts, LifecycleStatus = row.LifecycleStatus, LifecycleStatusLabel = row.LifecycleStatusLabel, LegacyStatus = row.LegacyStatus, DispatcherId = row.DispatcherId, DispatcherName = row.DispatcherName, Initials = row.Initials, Color = row.Color, DueDate = row.DueDate, ScheduledDate = row.ScheduledDate, TargetWeek = row.TargetWeek, ScheduleWeekOnly = row.ScheduleWeekOnly, DayGroup = row.DayGroup, VendorId = row.VendorId, VendorName = row.VendorName, PrimaryDispatchStatus = row.PrimaryDispatchStatus, TechName = row.TechName, TechPhone = row.TechPhone, ApptDate = row.ApptDate, ApptTime = row.ApptTime, Pm = row.Pm, ServiceNotes = extended?.ServiceNotes ?? row.ServiceNotes, ExtraServices = ParseExtraServices(extended?.ExtraServices) ?? row.ExtraServices, DocStatus = row.DocStatus, FlagColor = row.FlagColor, Severity = row.Severity, PrimaryDispatchId = row.PrimaryDispatchId, RowVersion = row.RowVersion, DispatchRowVersion = row.DispatchRowVersion, PendingUpliftCount = row.PendingUpliftCount, UpliftSummary = row.UpliftSummary, Description = extended?.Description, Trade = extended?.Trade, Problem = extended?.Problem, SubTrade = extended?.SubTrade, OriginalWeek = row.OriginalWeek ?? extended?.OriginalWeek, OriginalDate = row.OriginalDate ?? extended?.OriginalDate, CompletedDate = extended?.CompletedDate, VendorNotes = extended?.VendorNotes }; } private static List? ParseExtraServices(string? json) { if (string.IsNullOrWhiteSpace(json)) return null; try { var list = System.Text.Json.JsonSerializer.Deserialize>(json); return list is { Count: > 0 } ? list : null; } catch (System.Text.Json.JsonException) { return null; } } private static WorkOrderCompletionDto MapCompletion( WorkOrderDetailExtendedFields? extended, Data.SeaHavenIndustries.Enums.DocStatus? docStatus, CompletionDocTemplateRow? template, IReadOnlyList signoffs) { return new WorkOrderCompletionDto { DocStatus = docStatus, Template = template == null ? null : MapTemplate(template), SignOffName = extended?.SignOffName, SignOffAttachment = extended?.SignOffAttachment, SignOffSignature = extended?.SignOffSignature, DispatchSignoffs = signoffs.Select(s => new DispatchSignoffDto { DispatchId = s.DispatchId, SignoffType = s.SignoffType, Name = s.Name, SignedAt = s.SignedAt?.ToUniversalTime().ToString("o") }).ToList() }; } internal static CompletionDocTemplateDto MapTemplate(CompletionDocTemplateRow template) => new() { Id = template.Id, Name = template.Name, ServiceKey = template.ServiceKey, WorkOrderType = template.WorkOrderType, TemplateUrl = template.TemplateUrl, IsActive = template.IsActive }; } }