using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Interfaces; using SeaHaven.DataServices.Models; namespace SeaHaven.DataServices.Implementation { public class VendorOperationsDataService : IVendorOperationsDataService { private static readonly string[] TerminalDispatchStatuses = { "Completed", "Verified", "Cancelled", "Canceled" }; private readonly ApplicationDbContext _context; private readonly IDispatchDataService _dispatchDataService; public VendorOperationsDataService(ApplicationDbContext context, IDispatchDataService dispatchDataService) { _context = context; _dispatchDataService = dispatchDataService; } public async Task> GetAssignedWorkOrderIdsAsync(CancellationToken cancellationToken) { var directlyAssignedIds = await _context.Dispatches .AsNoTracking() .Where(d => !TerminalDispatchStatuses.Contains(d.Status ?? "") && d.WorkOrderId.HasValue) .Select(d => d.WorkOrderId!.Value) .ToListAsync(cancellationToken); var multiplyAssignedIds = await _context.DispatchWorkOrders .AsNoTracking() .Where(link => link.Dispatch != null && !TerminalDispatchStatuses.Contains(link.Dispatch.Status ?? "")) .Select(link => link.WorkOrderId) .ToListAsync(cancellationToken); return directlyAssignedIds.Concat(multiplyAssignedIds).Distinct().ToList(); } public async Task> GetNoVendorWorkOrdersAsync( DateTime cutoff, IReadOnlyCollection excludeIds, CancellationToken cancellationToken) { return await _context.workOrders .AsNoTracking() .Where(wo => wo.ScheduledDate.HasValue && wo.ScheduledDate.Value <= cutoff && wo.LifecycleStatus != LifecycleStatus.Completed && wo.LifecycleStatus != LifecycleStatus.Canceled && !excludeIds.Contains(wo.Id)) .OrderBy(wo => wo.ScheduledDate) .Select(wo => new NoVendorCandidateReadModel { Id = wo.Id, InternalWONumber = wo.InternalWONumber, WorkerOrderNumber = wo.WorkerOrderNumber, ScheduledDate = wo.ScheduledDate }) .ToListAsync(cancellationToken); } public async Task> GetActiveScheduledDispatchesAsync(CancellationToken cancellationToken) { var direct = _context.Dispatches.AsNoTracking() .Where(dispatch => !TerminalDispatchStatuses.Contains(dispatch.Status ?? "") && (dispatch.IsDeleted == null || dispatch.IsDeleted == false) && dispatch.WorkOrderId.HasValue && (dispatch.ScheduledDate.HasValue || (dispatch.WorkOrder != null && dispatch.WorkOrder.ScheduledStart.HasValue))) .Select(dispatch => new ScheduledDispatchReadModel { Id = dispatch.Id, VendorId = dispatch.VendorId, VendorName = dispatch.Vendor!.CompanyName, WorkOrderId = dispatch.WorkOrderId!.Value, WorkOrderNumber = dispatch.WorkOrder!.InternalWONumber ?? dispatch.WorkOrder.WorkerOrderNumber, Start = dispatch.WorkOrder!.ScheduledStart ?? dispatch.ScheduledDate!.Value, End = dispatch.WorkOrder.ScheduledEnd ?? (dispatch.WorkOrder.ScheduledStart ?? dispatch.ScheduledDate!.Value).AddHours(2) }); var linked = _context.DispatchWorkOrders.AsNoTracking() .Where(link => link.Dispatch != null && link.WorkOrder != null && link.Dispatch.WorkOrderId != link.WorkOrderId && !TerminalDispatchStatuses.Contains(link.Dispatch.Status ?? "") && (link.Dispatch.IsDeleted == null || link.Dispatch.IsDeleted == false) && (link.Dispatch.ScheduledDate.HasValue || link.WorkOrder.ScheduledStart.HasValue)) .Select(link => new ScheduledDispatchReadModel { Id = link.DispatchId, VendorId = link.Dispatch!.VendorId, VendorName = link.Dispatch.Vendor!.CompanyName, WorkOrderId = link.WorkOrderId, WorkOrderNumber = link.WorkOrder!.InternalWONumber ?? link.WorkOrder.WorkerOrderNumber, Start = link.WorkOrder.ScheduledStart ?? link.Dispatch.ScheduledDate!.Value, End = link.WorkOrder.ScheduledEnd ?? (link.WorkOrder.ScheduledStart ?? link.Dispatch.ScheduledDate!.Value).AddHours(2) }); return await direct.Concat(linked).ToListAsync(cancellationToken); } public async Task> GetActiveVendorsAsync(CancellationToken cancellationToken) { return await _context.Vendors.AsNoTracking() .Where(v => v.IsActive && (v.IsDeleted == null || v.IsDeleted == false)) .Select(v => new ActiveVendorReadModel { Id = v.Id, CompanyName = v.CompanyName, ContactName = v.ContactName, TradeSpecialties = v.TradeSpecialties, AvailabilityStatus = v.AvailabilityStatus, AvailabilityUpdatedAt = v.AvailabilityUpdatedAt }) .ToListAsync(cancellationToken); } public async Task> GetSitePreferredVendorsAsync(int locationId, CancellationToken cancellationToken) { return await _context.SitePreferredVendors .AsNoTracking() .Where(p => p.LocationId == locationId) .Include(p => p.Vendor) .OrderBy(p => p.SortOrder) .ToListAsync(cancellationToken); } public async Task> GetSitePreferredVendorsForReplaceAsync(int locationId, CancellationToken cancellationToken) { return await _context.SitePreferredVendors .Where(p => p.LocationId == locationId) .ToListAsync(cancellationToken); } public Task LocationExistsAsync(int locationId, CancellationToken cancellationToken) => _context.Locations.AnyAsync(location => location.Id == locationId, cancellationToken); public async Task> GetVendorsByIdsAsync(IReadOnlyCollection vendorIds, CancellationToken cancellationToken) => await _context.Vendors.Where(vendor => vendorIds.Contains(vendor.Id)) .ToDictionaryAsync(vendor => vendor.Id, cancellationToken); public async Task> GetInsightDispatchDataAsync( DateTime? from, DateTime? to, int? locationId, string? trade, int? vendorId, CancellationToken cancellationToken) { var query = _context.Dispatches.AsNoTracking() .Include(dispatch => dispatch.Vendor) .Include(dispatch => dispatch.WorkOrder) .Where(dispatch => dispatch.Vendor != null); if (from.HasValue) query = query.Where(dispatch => (dispatch.DispatchedAt ?? dispatch.CreatedDate) >= from); if (to.HasValue) query = query.Where(dispatch => (dispatch.DispatchedAt ?? dispatch.CreatedDate) <= to); if (locationId.HasValue) query = query.Where(dispatch => dispatch.WorkOrder!.LocationId == locationId); if (!string.IsNullOrWhiteSpace(trade)) query = query.Where(dispatch => dispatch.WorkOrder!.Trade == trade); if (vendorId.HasValue) query = query.Where(dispatch => dispatch.VendorId == vendorId); return await query.Select(dispatch => new InsightDispatchData { VendorId = dispatch.VendorId, VendorName = dispatch.Vendor!.CompanyName, Status = dispatch.Status, DispatchedAt = dispatch.DispatchedAt, CreatedDate = dispatch.CreatedDate, ScheduledDate = dispatch.ScheduledDate, AcknowledgedAt = dispatch.AcknowledgedAt, CompletedDate = dispatch.CompletedDate, DueDate = dispatch.WorkOrder != null ? dispatch.WorkOrder.DueDate : null }).ToListAsync(cancellationToken); } public Task GetVendorForUpdateAsync(int vendorId, CancellationToken cancellationToken) => _context.Vendors.FirstOrDefaultAsync(v => v.Id == vendorId, cancellationToken); public Task GetCompletionDocumentForUpdateAsync(int documentId, CancellationToken cancellationToken) => _context.VendorCompletionDocuments.FirstOrDefaultAsync(d => d.Id == documentId, cancellationToken); public Task GetDispatchForUpdateAsync(int dispatchId, CancellationToken cancellationToken) => _context.Dispatches.FirstOrDefaultAsync(d => d.Id == dispatchId, cancellationToken); public Task GetWorkOrderForUpdateAsync(int workOrderId, CancellationToken cancellationToken) => _context.workOrders.FirstOrDefaultAsync(w => w.Id == workOrderId, cancellationToken); public async Task GetExistingActiveDispatchForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) { var linkedDispatchIds = _context.DispatchWorkOrders .Where(link => link.WorkOrderId == workOrderId) .Select(link => link.DispatchId); return await _context.Dispatches .Where(item => (item.WorkOrderId == workOrderId || linkedDispatchIds.Contains(item.Id)) && !TerminalDispatchStatuses.Contains(item.Status ?? "")) .OrderByDescending(item => item.CreatedDate) .FirstOrDefaultAsync(cancellationToken); } public Task GenerateNextDispatchNumberAsync(CancellationToken cancellationToken) => _dispatchDataService.GenerateNextDispatchNumberAsync(); public Task GenerateNextPONumberAsync(CancellationToken cancellationToken) => _dispatchDataService.GenerateNextPONumberAsync(); public Task AddDispatchAsync(Dispatch dispatch, CancellationToken cancellationToken) { _context.Dispatches.Add(dispatch); return Task.CompletedTask; } public Task StageWorkOrderAuditAsync(WorkOrderAuditLog log, CancellationToken cancellationToken) { _context.WorkOrderAuditLogs.Add(log); return Task.CompletedTask; } public Task StageVendorAuditAsync(VendorAuditLog log, CancellationToken cancellationToken) { _context.VendorAuditLogs.Add(log); return Task.CompletedTask; } public Task AddSitePreferredVendorAsync(SitePreferredVendor preference, CancellationToken cancellationToken) { _context.SitePreferredVendors.Add(preference); return Task.CompletedTask; } public Task RemoveSitePreferredVendorsAsync(IEnumerable preferences, CancellationToken cancellationToken) { _context.SitePreferredVendors.RemoveRange(preferences); return Task.CompletedTask; } public Task SaveChangesAsync(CancellationToken cancellationToken) => _context.SaveChangesAsync(cancellationToken); } }