2026-07-24 17:35:34 -03:00
|
|
|
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<IReadOnlyList<int>> GetAssignedWorkOrderIdsAsync(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var directlyAssignedIds = await _context.Dispatches
|
|
|
|
|
.AsNoTracking()
|
2026-09-18 12:59:56 -03:00
|
|
|
.Where(d => !TerminalDispatchStatuses.Contains(d.Status ?? "")
|
|
|
|
|
&& (d.IsDeleted == null || d.IsDeleted == false)
|
|
|
|
|
&& d.WorkOrderId.HasValue)
|
2026-07-24 17:35:34 -03:00
|
|
|
.Select(d => d.WorkOrderId!.Value)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var multiplyAssignedIds = await _context.DispatchWorkOrders
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
.Where(link => link.Dispatch != null
|
2026-09-18 12:59:56 -03:00
|
|
|
&& (link.Dispatch.IsDeleted == null || link.Dispatch.IsDeleted == false)
|
2026-07-24 17:35:34 -03:00
|
|
|
&& !TerminalDispatchStatuses.Contains(link.Dispatch.Status ?? ""))
|
|
|
|
|
.Select(link => link.WorkOrderId)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
return directlyAssignedIds.Concat(multiplyAssignedIds).Distinct().ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<IReadOnlyList<NoVendorCandidateReadModel>> GetNoVendorWorkOrdersAsync(
|
|
|
|
|
DateTime cutoff, IReadOnlyCollection<int> 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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:40:33 -03:00
|
|
|
public Task<IReadOnlyList<ScheduledDispatchReadModel>> GetActiveScheduledDispatchesAsync(CancellationToken cancellationToken)
|
|
|
|
|
=> GetActiveScheduledDispatchesAsync(null, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public async Task<IReadOnlyList<ScheduledDispatchReadModel>> GetActiveScheduledDispatchesAsync(
|
|
|
|
|
int? accountId, CancellationToken cancellationToken)
|
2026-07-24 17:35:34 -03:00
|
|
|
{
|
2026-09-18 12:40:33 -03:00
|
|
|
var directDispatches = _context.Dispatches.AsNoTracking()
|
2026-07-24 17:35:34 -03:00
|
|
|
.Where(dispatch => !TerminalDispatchStatuses.Contains(dispatch.Status ?? "")
|
|
|
|
|
&& (dispatch.IsDeleted == null || dispatch.IsDeleted == false)
|
|
|
|
|
&& dispatch.WorkOrderId.HasValue
|
|
|
|
|
&& (dispatch.ScheduledDate.HasValue
|
2026-09-18 12:40:33 -03:00
|
|
|
|| (dispatch.WorkOrder != null && dispatch.WorkOrder.ScheduledStart.HasValue)));
|
|
|
|
|
if (accountId is int directAccountId)
|
|
|
|
|
directDispatches = directDispatches.Where(dispatch =>
|
|
|
|
|
dispatch.WorkOrder != null && dispatch.WorkOrder.AccountId == directAccountId);
|
|
|
|
|
|
|
|
|
|
var direct = directDispatches
|
2026-07-24 17:35:34 -03:00
|
|
|
.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,
|
2026-09-18 12:40:33 -03:00
|
|
|
AssignTo = dispatch.WorkOrder.AssignTo,
|
2026-07-24 17:35:34 -03:00
|
|
|
Start = dispatch.WorkOrder!.ScheduledStart ?? dispatch.ScheduledDate!.Value,
|
|
|
|
|
End = dispatch.WorkOrder.ScheduledEnd
|
|
|
|
|
?? (dispatch.WorkOrder.ScheduledStart ?? dispatch.ScheduledDate!.Value).AddHours(2)
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-18 12:40:33 -03:00
|
|
|
var linkedDispatches = _context.DispatchWorkOrders.AsNoTracking()
|
2026-07-24 17:35:34 -03:00
|
|
|
.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)
|
2026-09-18 12:40:33 -03:00
|
|
|
&& (link.Dispatch.ScheduledDate.HasValue || link.WorkOrder.ScheduledStart.HasValue));
|
|
|
|
|
if (accountId is int linkedAccountId)
|
|
|
|
|
linkedDispatches = linkedDispatches.Where(link => link.WorkOrder!.AccountId == linkedAccountId);
|
|
|
|
|
|
|
|
|
|
var linked = linkedDispatches
|
2026-07-24 17:35:34 -03:00
|
|
|
.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,
|
2026-09-18 12:40:33 -03:00
|
|
|
AssignTo = link.WorkOrder.AssignTo,
|
2026-07-24 17:35:34 -03:00
|
|
|
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<IReadOnlyList<ActiveVendorReadModel>> 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<IReadOnlyList<SitePreferredVendor>> 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<List<SitePreferredVendor>> GetSitePreferredVendorsForReplaceAsync(int locationId, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
return await _context.SitePreferredVendors
|
|
|
|
|
.Where(p => p.LocationId == locationId)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<bool> LocationExistsAsync(int locationId, CancellationToken cancellationToken)
|
2026-09-25 11:53:29 -03:00
|
|
|
=> _context.Locations.AnyAsync(location => location.Id == locationId && location.IsDeleted != true, cancellationToken);
|
2026-07-24 17:35:34 -03:00
|
|
|
|
|
|
|
|
public async Task<Dictionary<int, Vendor>> GetVendorsByIdsAsync(IReadOnlyCollection<int> vendorIds, CancellationToken cancellationToken)
|
|
|
|
|
=> await _context.Vendors.Where(vendor => vendorIds.Contains(vendor.Id))
|
|
|
|
|
.ToDictionaryAsync(vendor => vendor.Id, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public async Task<IReadOnlyList<InsightDispatchData>> 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<Vendor?> GetVendorForUpdateAsync(int vendorId, CancellationToken cancellationToken)
|
|
|
|
|
=> _context.Vendors.FirstOrDefaultAsync(v => v.Id == vendorId, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public Task<VendorCompletionDocument?> GetCompletionDocumentForUpdateAsync(int documentId, CancellationToken cancellationToken)
|
|
|
|
|
=> _context.VendorCompletionDocuments.FirstOrDefaultAsync(d => d.Id == documentId, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public Task<Dispatch?> GetDispatchForUpdateAsync(int dispatchId, CancellationToken cancellationToken)
|
|
|
|
|
=> _context.Dispatches.FirstOrDefaultAsync(d => d.Id == dispatchId, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public Task<WorkOrder?> GetWorkOrderForUpdateAsync(int workOrderId, CancellationToken cancellationToken)
|
|
|
|
|
=> _context.workOrders.FirstOrDefaultAsync(w => w.Id == workOrderId, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public async Task<Dispatch?> 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<string> GenerateNextDispatchNumberAsync(CancellationToken cancellationToken)
|
|
|
|
|
=> _dispatchDataService.GenerateNextDispatchNumberAsync();
|
|
|
|
|
|
|
|
|
|
public Task<string> 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<SitePreferredVendor> preferences, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
_context.SitePreferredVendors.RemoveRange(preferences);
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<int> SaveChangesAsync(CancellationToken cancellationToken)
|
|
|
|
|
=> _context.SaveChangesAsync(cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
}
|