using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Api.SeaHavenIndustries.Controllers { [Authorize] [ApiController] [Route("api/[controller]")] [Route("api/dispatches")] public class DispatchController : Controller { private readonly ApplicationDbContext _db; public DispatchController(ApplicationDbContext db) { _db = db; } [HttpGet] public async Task List( [FromQuery] string? search = null, [FromQuery] string? status = null, [FromQuery] int? vendorId = null, [FromQuery] DateTime? dateFrom = null, [FromQuery] DateTime? dateTo = null, [FromQuery] string? sortBy = "dispatchedAt", [FromQuery] string? sortDir = "desc", [FromQuery] int page = 1, [FromQuery] int pageSize = 25) { var q = _db.Dispatches .Include(d => d.Vendor) .Include(d => d.WorkOrder).ThenInclude(w => w!.Locations) .Where(d => d.IsDeleted == null || d.IsDeleted == false); if (!string.IsNullOrWhiteSpace(search)) { var s = search.ToLower(); q = q.Where(d => (d.DispatchNumber ?? "").ToLower().Contains(s) || (d.PONumber ?? "").ToLower().Contains(s) || (d.Vendor != null && (d.Vendor.CompanyName ?? "").ToLower().Contains(s)) || (d.WorkOrder != null && (d.WorkOrder.InternalWONumber ?? "").ToLower().Contains(s)) || (d.WorkOrder != null && (d.WorkOrder.WorkerOrderTitle ?? "").ToLower().Contains(s)) || (d.WorkOrder != null && d.WorkOrder.Locations != null && (d.WorkOrder.Locations.Name ?? "").ToLower().Contains(s)) ); } if (!string.IsNullOrWhiteSpace(status)) q = q.Where(d => d.Status == status); if (vendorId.HasValue) q = q.Where(d => d.VendorId == vendorId.Value); if (dateFrom.HasValue) q = q.Where(d => d.DispatchedAt >= dateFrom.Value); if (dateTo.HasValue) { var end = dateTo.Value.Date.AddDays(1); q = q.Where(d => d.DispatchedAt < end); } var total = await q.CountAsync(); var dir = (sortDir ?? "desc").ToLowerInvariant() == "asc"; switch ((sortBy ?? "").ToLowerInvariant()) { case "ponumber": q = dir ? q.OrderBy(d => d.PONumber) : q.OrderByDescending(d => d.PONumber); break; case "dispatchnumber": q = dir ? q.OrderBy(d => d.DispatchNumber) : q.OrderByDescending(d => d.DispatchNumber); break; case "vendor": q = dir ? q.OrderBy(d => d.Vendor!.CompanyName) : q.OrderByDescending(d => d.Vendor!.CompanyName); break; case "nte": case "nteamount": q = dir ? q.OrderBy(d => d.NTEAmount) : q.OrderByDescending(d => d.NTEAmount); break; case "status": q = dir ? q.OrderBy(d => d.Status) : q.OrderByDescending(d => d.Status); break; case "scheduleddate": q = dir ? q.OrderBy(d => d.ScheduledDate) : q.OrderByDescending(d => d.ScheduledDate); break; case "completeddate": q = dir ? q.OrderBy(d => d.CompletedDate) : q.OrderByDescending(d => d.CompletedDate); break; default: q = dir ? q.OrderBy(d => d.DispatchedAt) : q.OrderByDescending(d => d.DispatchedAt); break; } var rows = await q .Skip((Math.Max(page, 1) - 1) * pageSize) .Take(pageSize) .Select(d => new { d.Id, d.DispatchNumber, d.PONumber, d.NTEAmount, d.Status, d.DispatchedAt, d.ScheduledDate, d.CompletedDate, d.AcknowledgedAt, d.VerifiedAt, VendorId = d.VendorId, VendorName = d.Vendor != null ? d.Vendor.CompanyName : null, WorkOrderId = d.WorkOrderId, PrimaryWOInternalNumber = d.WorkOrder != null ? d.WorkOrder.InternalWONumber : null, PrimaryWOTitle = d.WorkOrder != null ? d.WorkOrder.WorkerOrderTitle : null, LocationName = d.WorkOrder != null && d.WorkOrder.Locations != null ? d.WorkOrder.Locations.Name : null, WorkOrderCount = _db.DispatchWorkOrders.Count(dwo => dwo.DispatchId == d.Id), PendingUpliftCount = _db.DispatchUpliftRequests.Count(u => u.DispatchId == d.Id && u.Status == "Pending" && (u.IsDeleted == null || u.IsDeleted == false)) }) .ToListAsync(); return Ok(new DataResponse { Status = "Success", Data = new { total, page, pageSize, items = rows } }); } [HttpGet("{id:int}")] public async Task GetById(int id) { var dispatch = await _db.Dispatches .Include(d => d.Vendor) .Include(d => d.WorkOrder).ThenInclude(w => w!.Locations) .FirstOrDefaultAsync(d => d.Id == id); if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" }); var linkedWOs = await _db.DispatchWorkOrders .Where(dwo => dwo.DispatchId == id) .Select(dwo => new { dwo.WorkOrderId, InternalWONumber = dwo.WorkOrder != null ? dwo.WorkOrder.InternalWONumber : null, Title = dwo.WorkOrder != null ? dwo.WorkOrder.WorkerOrderTitle : null, Priority = dwo.WorkOrder != null ? dwo.WorkOrder.Priority : null, Status = dwo.WorkOrder != null ? dwo.WorkOrder.Status : null, LocationName = dwo.WorkOrder != null && dwo.WorkOrder.Locations != null ? dwo.WorkOrder.Locations.Name : null, Trade = dwo.WorkOrder != null ? dwo.WorkOrder.Trade : null, SubTrade = dwo.WorkOrder != null ? dwo.WorkOrder.SubTrade : null, Problem = dwo.WorkOrder != null ? dwo.WorkOrder.Problem : null }).ToListAsync(); if (linkedWOs.Count == 0 && dispatch.WorkOrderId.HasValue) { var wo = await _db.workOrders .Include(w => w.Locations) .FirstOrDefaultAsync(w => w.Id == dispatch.WorkOrderId.Value); if (wo != null) { linkedWOs = new[] { new { WorkOrderId = wo.Id, InternalWONumber = wo.InternalWONumber, Title = wo.WorkerOrderTitle, Priority = wo.Priority, Status = wo.Status, LocationName = wo.Locations != null ? wo.Locations.Name : null, Trade = wo.Trade, SubTrade = wo.SubTrade, Problem = wo.Problem }}.ToList(); } } var checklist = await _db.DispatchChecklistItems .Where(c => c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false)) .OrderBy(c => c.SortOrder) .Select(c => new { c.Id, c.ItemText, c.IsCompleted, c.CompletedBy, c.CompletedAt, c.SortOrder, c.WorkOrderId }) .ToListAsync(); var signoffs = await _db.DispatchSignoffs .Where(s => s.DispatchId == id && (s.IsDeleted == null || s.IsDeleted == false)) .Select(s => new { s.Id, s.SignoffType, s.Name, s.Signature, s.SignatureMethod, s.SignedAt }) .ToListAsync(); var rawComments = await (from c in _db.Comments where c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false) join u in _db.Users on c.UserId equals u.Id into users from u in users.DefaultIfEmpty() orderby c.CreatedDate select new { c.Id, c.Commenttext, c.Commenter, c.CommentType, c.RecordType, c.CreatedDate, c.UserId, UserFirstName = u != null ? u.FirstName : null, UserLastName = u != null ? u.LastName : null }) .ToListAsync(); var comments = rawComments.Select(c => new { c.Id, c.Commenttext, c.Commenter, c.CommentType, c.RecordType, c.CreatedDate, UserName = string.Join(" ", new[] { c.UserFirstName, c.UserLastName } .Where(s => !string.IsNullOrWhiteSpace(s))).Trim() }).ToList(); var rawUplifts = await (from u in _db.DispatchUpliftRequests where u.DispatchId == id && (u.IsDeleted == null || u.IsDeleted == false) join dec in _db.Users on u.DecidedByUserId equals dec.Id into decs from dec in decs.DefaultIfEmpty() orderby u.CreatedDate descending select new { u.Id, u.CurrentNTE, u.RequestedNTE, u.VendorReason, u.Status, u.RequiredTier, u.RequestedByVendorName, RequestedAt = u.CreatedDate, u.DecidedAt, u.DecisionNote, DecidedByFirstName = dec != null ? dec.FirstName : null, DecidedByLastName = dec != null ? dec.LastName : null }) .ToListAsync(); var upliftRequests = rawUplifts.Select(u => new { u.Id, u.CurrentNTE, u.RequestedNTE, Delta = u.RequestedNTE - (u.CurrentNTE ?? 0m), u.VendorReason, u.Status, u.RequiredTier, u.RequestedByVendorName, u.RequestedAt, u.DecidedAt, u.DecisionNote, DecidedByName = string.Join(" ", new[] { u.DecidedByFirstName, u.DecidedByLastName } .Where(s => !string.IsNullOrWhiteSpace(s))).Trim() }).ToList(); var dispatchNumber = dispatch.DispatchNumber ?? ""; var workOrderIds = linkedWOs.Select(l => l.WorkOrderId).Where(wid => wid > 0).ToList(); var rawAudit = await (from a in _db.WorkOrderAuditLogs where workOrderIds.Contains(a.WorkOrderId) && a.FieldName != null && a.FieldName.Contains(dispatchNumber) join u in _db.Users on a.UserId equals u.Id into users from u in users.DefaultIfEmpty() orderby a.CreatedAt descending select new { a.Id, a.Action, a.FieldName, a.OldValue, a.NewValue, a.CreatedAt, a.UserId, UserFirstName = u != null ? u.FirstName : null, UserLastName = u != null ? u.LastName : null }) .ToListAsync(); var auditLog = rawAudit.Select(a => new { Id = a.Id, Action = a.Action, FieldName = a.FieldName, OldValue = a.OldValue, NewValue = a.NewValue, CreatedAt = a.CreatedAt, UserName = string.Join(" ", new[] { a.UserFirstName, a.UserLastName } .Where(s => !string.IsNullOrWhiteSpace(s))).Trim() }).ToList(); return Ok(new DataResponse { Status = "Success", Data = new { dispatch.Id, dispatch.DispatchNumber, dispatch.PONumber, dispatch.NTEAmount, dispatch.Description, dispatch.Status, dispatch.ScheduledDate, dispatch.CompletedDate, dispatch.DispatchedAt, dispatch.AcknowledgedAt, dispatch.VerifiedAt, dispatch.VerifiedBy, dispatch.EmailSent, dispatch.WorkOrderId, Vendor = dispatch.Vendor == null ? null : new { dispatch.Vendor.Id, dispatch.Vendor.CompanyName, dispatch.Vendor.ContactName, dispatch.Vendor.Email, dispatch.Vendor.Phone, dispatch.Vendor.Address, dispatch.Vendor.City, dispatch.Vendor.State, dispatch.Vendor.Zip, dispatch.Vendor.TradeSpecialties }, PrimaryLocation = dispatch.WorkOrder?.Locations == null ? null : new { dispatch.WorkOrder.Locations.Id, dispatch.WorkOrder.Locations.Name, dispatch.WorkOrder.Locations.Address1, dispatch.WorkOrder.Locations.City, dispatch.WorkOrder.Locations.State, dispatch.WorkOrder.Locations.Zip }, WorkOrders = linkedWOs, Checklist = checklist, Signoffs = signoffs, Comments = comments, UpliftRequests = upliftRequests, AuditLog = auditLog } }); } } }