using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Helpers; using SeaHaven.DataServices.Interfaces; namespace SeaHaven.DataServices.Implementation { public class LocationDataService : ILocationDataService { private readonly ApplicationDbContext _context; public LocationDataService(ApplicationDbContext context) { _context = context; } public async Task GetByIdAsync(int id) { return await _context.Locations.FirstOrDefaultAsync(l => l.Id == id && l.IsDeleted != true); } public async Task GetByIdWithDetailsAsync(int id) { return await _context.Locations .FirstOrDefaultAsync(l => l.Id == id && l.IsDeleted != true); } public async Task> GetAllAsync() { return await _context.Locations.Where(l => l.IsDeleted != true).ToListAsync(); } public async Task> GetByAccountIdAsync(int accountId) { return await _context.Locations .AsNoTracking() .Where(l => l.AccountId == accountId && l.IsDeleted != true) .ToListAsync(); } public async Task<(bool Exists, int? AccountId)> GetAccountScopeAsync( int locationId, CancellationToken cancellationToken = default) { var row = await _context.Locations .AsNoTracking() .Where(l => l.Id == locationId && l.IsDeleted != true) .Select(l => new { l.AccountId }) .FirstOrDefaultAsync(cancellationToken); return row == null ? (false, null) : (true, row.AccountId); } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( int page, int pageSize, string? search = null) { var query = _context.Locations.Where(l => l.IsDeleted != true); if (!string.IsNullOrWhiteSpace(search)) { query = query.Where(l => l.Name!.Contains(search) || l.City!.Contains(search)); } var totalCount = await query.CountAsync(); var items = await query .OrderBy(l => l.Name) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return (items, totalCount); } public async Task> GetSiteOptionsAsync( string? search = null, CancellationToken cancellationToken = default) { var query = _context.Locations .AsNoTracking() .Where(l => l.AccountId != null && l.IsDeleted != true); if (!string.IsNullOrWhiteSpace(search)) { var s = search.ToLower(); query = query.Where(l => (l.Name ?? "").ToLower().Contains(s) || (l.City ?? "").ToLower().Contains(s)); } return await query .OrderBy(l => l.Name) .Select(l => new SiteOptionRow(l.Id, l.Name, l.City, l.State)) .ToListAsync(cancellationToken); } public async Task AddAsync(Locations location) { location.CreatedDate = DateTime.Now; await _context.Locations.AddAsync(location); await _context.SaveChangesAsync(); return location; } public async Task UpdateAsync(Locations location) { location.LastModificationTime = DateTime.Now; _context.Locations.Update(location); await _context.SaveChangesAsync(); } public async Task DeleteAsync(int id) { var entity = await GetByIdAsync(id); if (entity != null) { _context.Locations.Remove(entity); await _context.SaveChangesAsync(); } } public async Task ExistsAsync(int id) { return await _context.Locations.AnyAsync(l => l.Id == id && l.IsDeleted != true); } public async Task CountAsync() { return await _context.Locations.CountAsync(l => l.IsDeleted != true); } public async Task<(List Items, int TotalCount)> GetListPagedAsync( int page, int pageSize, string? search, IReadOnlyCollection? states, CancellationToken cancellationToken, string? sortBy = null, string? sortDirection = null) { IQueryable query = _context.Locations .AsNoTracking() .Include(l => l.Account) .Where(l => l.IsDeleted != true); if (!string.IsNullOrWhiteSpace(search)) { query = query.Where(l => l.Name!.Contains(search) || l.Address1!.Contains(search) || l.City!.Contains(search) || l.Account != null && l.Account.Name!.Contains(search)); } if (states is { Count: > 0 }) { query = query.Where(l => l.State != null && states.Contains(l.State.Trim().ToUpper())); } query = ApplyListSort(query, sortBy, sortDirection); var totalCount = await query.CountAsync(cancellationToken); var items = await query .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(cancellationToken); return (items, totalCount); } private static IQueryable ApplyListSort(IQueryable query, string? sortBy, string? sortDirection) { var descending = string.Equals(sortDirection?.Trim(), "desc", StringComparison.OrdinalIgnoreCase); IOrderedQueryable ordered = (sortBy?.Trim().ToLowerInvariant()) switch { "code" => descending ? query.OrderByDescending(l => l.Name) : query.OrderBy(l => l.Name), "client" => descending ? query.OrderByDescending(l => l.Account!.Name) : query.OrderBy(l => l.Account!.Name), "address" => descending ? query.OrderByDescending(l => l.Address1) : query.OrderBy(l => l.Address1), "city" => descending ? query.OrderByDescending(l => l.City) : query.OrderBy(l => l.City), "state" => descending ? query.OrderByDescending(l => l.State) : query.OrderBy(l => l.State), "poc" => descending ? query.OrderByDescending(FirstActiveContactName) : query.OrderBy(FirstActiveContactName), _ => query.OrderBy(l => l.Name) }; return ordered.ThenBy(l => l.Id); } private static readonly System.Linq.Expressions.Expression> FirstActiveContactName = l => l.Contacts .Where(c => c.IsDeleted != true) .OrderBy(c => c.SiteContactOrder) .ThenBy(c => c.Id) .Select(c => c.FirstName) .FirstOrDefault(); public async Task GetDetailByIdAsync(int id, CancellationToken cancellationToken) { return await _context.Locations .AsNoTracking() .Include(l => l.Account) .Include(l => l.Contacts.Where(c => c.IsDeleted != true)) .FirstOrDefaultAsync(l => l.Id == id && l.IsDeleted != true, cancellationToken); } public async Task GetByIdForUpdateAsync(int id, CancellationToken cancellationToken) { return await _context.Locations .Include(l => l.Contacts) .FirstOrDefaultAsync(l => l.Id == id && l.IsDeleted != true, cancellationToken); } public async Task> GetSiteContactsByLocationIdsAsync( IReadOnlyCollection locationIds, CancellationToken cancellationToken) { if (locationIds.Count == 0) return Array.Empty(); return await _context.Contacts .AsNoTracking() .Where(c => c.LocationId != null && locationIds.Contains(c.LocationId.Value) && c.IsDeleted != true) .OrderBy(c => c.SiteContactOrder) .ThenBy(c => c.Id) .ToListAsync(cancellationToken); } public async Task AddAsync(Locations location, CancellationToken cancellationToken) { location.CreatedDate = DateTime.Now; await _context.Locations.AddAsync(location, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return location; } public async Task UpdateAsync(Locations location, CancellationToken cancellationToken) { location.LastModificationTime = DateTime.Now; _context.Locations.Update(location); await _context.SaveChangesAsync(cancellationToken); } public async Task SiteCodeExistsAsync( string siteCode, int? accountId, int? excludeLocationId, CancellationToken cancellationToken) { var normalized = siteCode.Trim().ToUpperInvariant(); return await _context.Locations .AsNoTracking() .Where(l => l.IsDeleted != true && l.AccountId == accountId && l.Name != null && l.Name.Trim().ToUpper() == normalized) .Where(l => excludeLocationId == null || l.Id != excludeLocationId) .AnyAsync(cancellationToken); } public async Task<(int Count, IReadOnlyList Ids)> GetOpenWorkOrderIdsAsync( int locationId, string? siteCode, int? siteAccountId, int? callerAccountId, int maxIds, CancellationToken cancellationToken) { var code = string.IsNullOrWhiteSpace(siteCode) ? null : siteCode.Trim().ToUpper(); var query = _context.workOrders .AsNoTracking() .Where(w => w.IsDeleted != true && (w.LocationId == locationId || (code != null && w.LocationId == null && w.SiteCode != null && w.SiteCode.Trim().ToUpper() == code && (w.AccountId == null || w.AccountId == siteAccountId)))); if (callerAccountId is int accountId) query = query.Where(w => w.AccountId == accountId); query = WorkOrderBoardQueryFilters.ApplyStatusFilter(query, OpenLifecycleStatuses); var count = await query.CountAsync(cancellationToken); if (count == 0) return (0, Array.Empty()); var ids = await query .OrderBy(w => w.Id) .Select(w => w.Id) .Take(maxIds) .ToListAsync(cancellationToken); return (count, ids); } private static readonly IReadOnlyList OpenLifecycleStatuses = Enum .GetValues() .Where(status => !LifecycleStatusSets.Terminal.Contains(status)) .ToList(); public async Task<(IEnumerable Items, int TotalCount)> GetAddressbookPagedAsync( int page, int pageSize, string? search = null) { search ??= ""; var query = _context.Locations.Where(l => l.IsDeleted != true); if (!string.IsNullOrWhiteSpace(search)) { var s = search.ToLower(); query = query.Where(w => (w.Name ?? "").ToLower().Contains(s) || (w.Title ?? "").ToLower().Contains(s)); } var totalCount = await query.CountAsync(); var data = await query .Select(n => new { n.Id, n.Name, n.Title }) .OrderByDescending(d => d.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return (data, totalCount); } } }