shoc-backend/SeaHaven.DataServices/Implementation/VendorOperationsDataService.cs
Alexandre Brandizzi 92dabbfbe3 Hide deleted sites from every location read; require the Add Site fields on create
- Legacy reads (by id, all, by client, paged, address book, exists, count) and the vendor
  preference site check now skip tombstoned sites
- Create requires a client, street address, city, state and at least one complete contact
2026-09-25 11:53:29 -03:00

249 lines
13 KiB
C#

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()
.Where(d => !TerminalDispatchStatuses.Contains(d.Status ?? "")
&& (d.IsDeleted == null || d.IsDeleted == false)
&& d.WorkOrderId.HasValue)
.Select(d => d.WorkOrderId!.Value)
.ToListAsync(cancellationToken);
var multiplyAssignedIds = await _context.DispatchWorkOrders
.AsNoTracking()
.Where(link => link.Dispatch != null
&& (link.Dispatch.IsDeleted == null || link.Dispatch.IsDeleted == false)
&& !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);
}
public Task<IReadOnlyList<ScheduledDispatchReadModel>> GetActiveScheduledDispatchesAsync(CancellationToken cancellationToken)
=> GetActiveScheduledDispatchesAsync(null, cancellationToken);
public async Task<IReadOnlyList<ScheduledDispatchReadModel>> GetActiveScheduledDispatchesAsync(
int? accountId, CancellationToken cancellationToken)
{
var directDispatches = _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)));
if (accountId is int directAccountId)
directDispatches = directDispatches.Where(dispatch =>
dispatch.WorkOrder != null && dispatch.WorkOrder.AccountId == directAccountId);
var direct = directDispatches
.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,
AssignTo = dispatch.WorkOrder.AssignTo,
Start = dispatch.WorkOrder!.ScheduledStart ?? dispatch.ScheduledDate!.Value,
End = dispatch.WorkOrder.ScheduledEnd
?? (dispatch.WorkOrder.ScheduledStart ?? dispatch.ScheduledDate!.Value).AddHours(2)
});
var linkedDispatches = _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));
if (accountId is int linkedAccountId)
linkedDispatches = linkedDispatches.Where(link => link.WorkOrder!.AccountId == linkedAccountId);
var linked = linkedDispatches
.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,
AssignTo = link.WorkOrder.AssignTo,
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)
=> _context.Locations.AnyAsync(location => location.Id == locationId && location.IsDeleted != true, cancellationToken);
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);
}
}