using Data.SeaHavenIndustries; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Helpers; using SeaHaven.DataServices.Interfaces; using SeaHaven.DataServices.Models; namespace SeaHaven.DataServices.Implementation { public class WorkOrderDataService : IWorkOrderDataService { private readonly ApplicationDbContext _context; public WorkOrderDataService(ApplicationDbContext context) { _context = context; } public async Task GetByIdAsync(int id) { return await _context.workOrders.FindAsync(id); } public async Task GetByIdWithDetailsAsync(int id) { return await _context.workOrders .Include(w => w.Locations) .Include(w => w.AssignToUser) .FirstOrDefaultAsync(w => w.Id == id); } public async Task GetByInternalNumberAsync(string internalNumber) { return await _context.workOrders .FirstOrDefaultAsync(w => w.InternalWONumber == internalNumber); } public async Task> GetAllAsync() { return await _context.workOrders.ToListAsync(); } public async Task> GetByLocationIdAsync(int locationId) { return await _context.workOrders .Where(w => w.LocationId == locationId) .ToListAsync(); } public async Task> GetByAssigneeAsync(string assignee) { return await _context.workOrders .Where(w => w.AssignTo == assignee) .ToListAsync(); } public async Task> GetByStatusAsync(string status) { return await _context.workOrders .Where(w => w.Status == status) .ToListAsync(); } public async Task> GetByPriorityAsync(string priority) { return await _context.workOrders .Where(w => w.Priority == priority) .ToListAsync(); } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( int page, int pageSize, string? search = null, string? status = null, string? priority = null) { var query = _context.workOrders.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) { query = query.Where(w => w.WorkerOrderNumber!.Contains(search) || w.WorkerOrderTitle!.Contains(search)); } if (!string.IsNullOrWhiteSpace(status)) { query = query.Where(w => w.Status == status); } if (!string.IsNullOrWhiteSpace(priority)) { query = query.Where(w => w.Priority == priority); } var totalCount = await query.CountAsync(); var items = await query .OrderByDescending(w => w.CreatedDate) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return (items, totalCount); } public async Task<(IEnumerable Items, int TotalCount)> GetWorkOrderListPagedAsync( int page, int pageSize, string? search = null, string? status = null, string? assignee = null, int? locationId = null, string? sortBy = "lastUpdated", string? sortDir = "desc", int? accountId = null) { search ??= ""; var query = _context.workOrders .Include(w => w.Locations) .Include(w => w.AssignToUser) .Where(w => w.istemplate != true); if (accountId.HasValue) query = WorkOrderBoardQueryFilters.ApplyAccountScope(query, accountId.Value); if (locationId.HasValue) query = query.Where(w => w.LocationId == locationId.Value); if (!string.IsNullOrWhiteSpace(status)) query = query.Where(w => w.Status == status); if (assignee == "__unassigned") query = query.Where(w => w.AssignTo == null || w.AssignTo == ""); else if (!string.IsNullOrWhiteSpace(assignee)) query = query.Where(w => w.AssignToUser != null && (w.AssignToUser.FirstName + " " + w.AssignToUser.LastName).Contains(assignee)); if (!string.IsNullOrWhiteSpace(search)) { query = query.Where(w => (w.InternalWONumber != null && w.InternalWONumber.Contains(search)) || (w.WorkerOrderNumber != null && w.WorkerOrderNumber.Contains(search)) || (w.WorkerOrderTitle != null && w.WorkerOrderTitle.Contains(search)) || (w.Locations != null && w.Locations.Title != null && w.Locations.Title.Contains(search)) || (w.Locations != null && w.Locations.Name != null && w.Locations.Name.Contains(search)) ); } var totalCount = await query.CountAsync(); var isAsc = sortDir?.ToLower() == "asc"; IOrderedQueryable ordered = (sortBy?.ToLower()) switch { "number" => isAsc ? query.OrderBy(w => w.InternalWONumber) : query.OrderByDescending(w => w.InternalWONumber), "title" => isAsc ? query.OrderBy(w => w.WorkerOrderTitle) : query.OrderByDescending(w => w.WorkerOrderTitle), "location" => isAsc ? query.OrderBy(w => w.Locations != null ? w.Locations.Name : "") : query.OrderByDescending(w => w.Locations != null ? w.Locations.Name : ""), "priority" => isAsc ? query.OrderBy(w => w.Priority) : query.OrderByDescending(w => w.Priority), "status" => isAsc ? query.OrderBy(w => w.Status) : query.OrderByDescending(w => w.Status), "createddate" => isAsc ? query.OrderBy(w => w.CreatedDate) : query.OrderByDescending(w => w.CreatedDate), "assignedto" => isAsc ? query.OrderBy(w => w.AssignToUser != null ? w.AssignToUser.FirstName : "") : query.OrderByDescending(w => w.AssignToUser != null ? w.AssignToUser.FirstName : ""), "lastupdated" => isAsc ? query.OrderBy(w => w.LastModificationTime) : query.OrderByDescending(w => w.LastModificationTime), _ => query.OrderByDescending(w => w.Id) }; var data = await ordered .Skip((page - 1) * pageSize) .Take(pageSize) .Select(w => new WorkOrderListItemReadModel { Id = w.Id, InternalWONumber = w.InternalWONumber, WorkOrderNumber = w.WorkerOrderNumber, Number = w.InternalWONumber ?? w.WorkerOrderNumber, Title = w.WorkerOrderTitle, Location = w.Locations != null ? w.Locations.Name : "", LocationName = w.Locations != null ? w.Locations.Name : "", Priority = w.Priority, Status = w.Status, CreatedDate = w.CreatedDate, DueDate = w.DueDate, AssignedTo = w.AssignToUser != null ? w.AssignToUser.FirstName + " " + w.AssignToUser.LastName : "", EmployeeName = w.AssignToUser != null ? w.AssignToUser.FirstName + " " + w.AssignToUser.LastName : "", LastUpdated = new[] { w.LastModificationTime, w.Comments!.Max(c => (DateTime?)c.CreatedDate), _context.WorkOrderAuditLogs.Where(a => a.WorkOrderId == w.Id).Max(a => (DateTime?)a.CreatedAt) }.Where(d => d.HasValue).Max() }) .ToListAsync(); return (data, totalCount); } public async Task AddAsync(WorkOrder workOrder) { workOrder.CreatedDate ??= DateTime.UtcNow; await _context.workOrders.AddAsync(workOrder); await _context.SaveChangesAsync(); return workOrder; } public async Task UpdateAsync(WorkOrder workOrder) { workOrder.LastModificationTime = DateTime.UtcNow; _context.workOrders.Update(workOrder); await _context.SaveChangesAsync(); } public async Task DeleteAsync(int id) { var entity = await GetByIdAsync(id); if (entity != null) { _context.workOrders.Remove(entity); await _context.SaveChangesAsync(); } } public async Task> GetWorkOrderContactsAsync(int workOrderId) { return await _context.WorkOrderContacts .Where(wc => wc.WorkorderId == workOrderId) .ToListAsync(); } public async Task> GetWorkOrderCategoriesAsync(int workOrderId) { return await _context.workOrderCategories .Where(wc => wc.WorkorderId == workOrderId) .ToListAsync(); } public async Task> GetWorkOrderAttachmentsAsync(int workOrderId) { return await _context.workOrderAttachments .Where(wa => wa.WorkorderId == workOrderId) .ToListAsync(); } public async Task> GetWorkOrderCommentsAsync(int workOrderId) { return await _context.Comments .Where(c => c.WorkerOrderId == workOrderId) .ToListAsync(); } public async Task> GetWorkOrderAuditLogsAsync(int workOrderId) { return await _context.WorkOrderAuditLogs .Where(a => a.WorkOrderId == workOrderId) .OrderByDescending(a => a.CreatedAt) .ToListAsync(); } public async Task AddWorkOrderContactsAsync(int workOrderId, IEnumerable contactIds) { var contacts = contactIds.Select(cid => new WorkOrderContacts { WorkorderId = workOrderId, ContactId = cid }); await _context.WorkOrderContacts.AddRangeAsync(contacts); await _context.SaveChangesAsync(); } public async Task AddWorkOrderCategoriesAsync(int workOrderId, IEnumerable categoryIds) { var categories = categoryIds.Select(catId => new WorkOrderCategories { WorkorderId = workOrderId, CategoryId = catId }); await _context.workOrderCategories.AddRangeAsync(categories); await _context.SaveChangesAsync(); } public async Task AddWorkOrderAttachmentAsync(WorkOrderAttachments attachment) { await _context.workOrderAttachments.AddAsync(attachment); await _context.SaveChangesAsync(); } public async Task AddAuditLogAsync(WorkOrderAuditLog auditLog) { await _context.WorkOrderAuditLogs.AddAsync(auditLog); await _context.SaveChangesAsync(); } public async Task AddAuditLogsAsync(IReadOnlyCollection auditLogs) { if (auditLogs.Count == 0) return; await _context.WorkOrderAuditLogs.AddRangeAsync(auditLogs); await _context.SaveChangesAsync(); } public async Task DeleteWorkOrderContactsAsync(int workOrderId) { var contacts = await _context.WorkOrderContacts .Where(wc => wc.WorkorderId == workOrderId) .ToListAsync(); if (contacts.Any()) { _context.WorkOrderContacts.RemoveRange(contacts); await _context.SaveChangesAsync(); } } public async Task DeleteWorkOrderCategoriesAsync(int workOrderId) { var categories = await _context.workOrderCategories .Where(wc => wc.WorkorderId == workOrderId) .ToListAsync(); if (categories.Any()) { _context.workOrderCategories.RemoveRange(categories); await _context.SaveChangesAsync(); } } public async Task DeleteWorkOrderAttachmentsAsync(int workOrderId) { var attachments = await _context.workOrderAttachments .Where(wa => wa.WorkorderId == workOrderId) .ToListAsync(); if (attachments.Any()) { _context.workOrderAttachments.RemoveRange(attachments); await _context.SaveChangesAsync(); } } public async Task ExistsAsync(int id) { return await _context.workOrders.AnyAsync(w => w.Id == id); } public async Task InternalNumberExistsAsync(string internalNumber, int? excludeId = null) { return await _context.workOrders.AnyAsync(w => w.InternalWONumber == internalNumber && (excludeId == null || w.Id != excludeId)); } public async Task GenerateNextInternalWONumberAsync() { return await Task.FromResult($"WO-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}"); } public async Task CountAsync() { return await _context.workOrders.CountAsync(); } public async Task CountByStatusAsync(string status) { return await _context.workOrders.CountAsync(w => w.Status == status); } public async Task CountByPriorityAsync(string priority) { return await _context.workOrders.CountAsync(w => w.Priority == priority); } public async Task CountByLocationAsync(int locationId) { return await _context.workOrders.CountAsync(w => w.LocationId == locationId); } public async Task> GetAllWithDetailsAsync(int? accountId = null) { IQueryable query = _context.workOrders .Where(w => w.istemplate != true); if (accountId.HasValue) query = WorkOrderBoardQueryFilters.ApplyAccountScope(query, accountId.Value); return await query .Include(w => w.Locations) .Include(w => w.AssignToUser) .AsSplitQuery() .ToListAsync(); } public async Task> GetByRequestedIdsWithDetailsAsync( IReadOnlyCollection ids, CancellationToken cancellationToken) { var orderedIds = ids.Distinct().ToList(); if (orderedIds.Count == 0) return Array.Empty(); var loaded = await _context.workOrders .AsNoTracking() .Where(w => orderedIds.Contains(w.Id) && w.istemplate != true) .Include(w => w.Locations) .ToListAsync(cancellationToken); var byId = loaded.ToDictionary(w => w.Id); var ordered = new List(orderedIds.Count); foreach (var id in orderedIds) { if (byId.TryGetValue(id, out var workOrder)) ordered.Add(workOrder); } return ordered; } public async Task GetWorkOrderDetailAsync(int id, int? accountId = null) { var query = _context.workOrders .AsNoTracking() .Where(w => w.Id == id); if (accountId.HasValue) query = WorkOrderBoardQueryFilters.ApplyAccountScope(query, accountId.Value); return await query .Select(s => new WorkOrderDetailReadModel { Id = s.Id, InternalWONumber = s.InternalWONumber, WorkerOrderNumber = s.WorkerOrderNumber, Title = s.WorkerOrderTitle, Description = s.Description, CreatedDate = s.CreatedDate, DueDate = s.DueDate, ScheduledDate = s.ScheduledDate, CompletedDate = s.CompletedDate, Status = s.Status, Assignee = s.AssignToUser != null ? s.AssignToUser.FirstName : "", Location = s.Locations != null ? s.Locations.Name : "", LocationId = s.LocationId, LocationZip = s.Locations != null ? (s.Locations.Zip ?? ExtractZip(s.Locations.Address1)) : "", Priority = s.Priority, Problem = s.Problem, Trade = s.Trade, SubTrade = s.SubTrade, VendorNTE = s.VendorNTE, Source = s.Source, PO = s.PO, TT = s.TT, Poc = s.WorkOrderContacts!.Select(c => new ContactReadModel { Id = c.Id, Name = c.POC != null ? (c.POC.FirstName + " " + c.POC.LastName).Trim() : "", Email = c.POC != null ? c.POC.Email : "", Phone = c.POC != null ? c.POC.PhoneNumber : "" }).ToList(), Category = s.WorkOrderCategories!.Select(c => new CategoryReadModel { Id = c.Id, Name = c.Category != null ? c.Category.Name : "" }).ToList(), Attachments = s.workOrderAttachments!.Select(a => new AttachmentReadModel { Id = a.Id, Attachment = a.Attachments }).ToList(), BeforPhotoIssue = s.BeforPhotoIssue, BeforPhotoAttachment = s.BeforPhotoAttachment, AfterPhotoIssue = s.AfterPhotoIssue, AfterPhotoAttachment = s.AfterPhotoAttachment, SignOffName = s.SignOffName, SignOffAttachment = s.SignOffAttachment, SignOffSignature = s.SignOffSignature, Comments = s.Comments!.Select(c => new CommentReadModel { Id = c.Id, CreatedDate = c.CreatedDate, Commenttext = c.Commenttext, Commenter = c.Commenter, RecordType = c.RecordType, CommentType = c.CommentType, Documents = c.Documents, UserName = c.ApplicationUser != null ? c.ApplicationUser.FirstName : "" }).ToList(), Quotes = s.Quotes!.Select(q => new QuoteRefReadModel { Code = q.QuotId, Id = q.Id }).ToList(), AuditLog = _context.WorkOrderAuditLogs .Where(a => a.WorkOrderId == s.Id) .OrderByDescending(a => a.CreatedAt) .Select(a => new AuditLogReadModel { Id = a.Id, FieldName = a.FieldName, OldValue = a.OldValue, NewValue = a.NewValue, Action = a.Action, CreatedAt = a.CreatedAt, UserName = a.User != null ? a.User.FirstName + " " + a.User.LastName : "" }).ToList(), Dispatches = s.Dispatches!.Select(d => new DispatchReadModel { Id = d.Id, DispatchNumber = d.DispatchNumber, PONumber = d.PONumber, NTEAmount = d.NTEAmount, Description = d.Description, Status = d.Status, ScheduledDate = d.ScheduledDate, CompletedDate = d.CompletedDate, DispatchedAt = d.DispatchedAt, EmailSent = d.EmailSent, VendorName = d.Vendor != null ? d.Vendor.CompanyName : "", VendorEmail = d.Vendor != null ? d.Vendor.Email : "", VendorId = d.VendorId }).ToList() }).FirstOrDefaultAsync(); } public async Task> GetNonTemplateWorkOrdersWithLocationsAsync(int? accountId = null) { IQueryable query = _context.workOrders .Where(w => w.istemplate != true); if (accountId.HasValue) query = WorkOrderBoardQueryFilters.ApplyAccountScope(query, accountId.Value); return await query .Include(w => w.Locations) .ToListAsync(); } private static string? ExtractZip(string? address) { if (string.IsNullOrWhiteSpace(address)) return null; var match = System.Text.RegularExpressions.Regex.Match(address, @"\b(\d{5})(?:-\d{4})?\b"); return match.Success ? match.Groups[1].Value : null; } } }