using Microsoft.EntityFrameworkCore; using PowderCoating.Core.Entities; using PowderCoating.Core.Enums; using PowderCoating.Core.Interfaces.Services; using PowderCoating.Infrastructure.Data; namespace PowderCoating.Infrastructure.Services; /// /// Implements using directly. /// Queries require IgnoreQueryFilters() (to bypass the tenant filter and see all companies), /// dynamic sort expressions, and cross-entity GROUP BY aggregations — all of which are beyond the /// generic . /// public class CompanyListService : ICompanyListService { private readonly ApplicationDbContext _context; public CompanyListService(ApplicationDbContext context) { _context = context; } /// public async Task<(List Companies, int TotalCount, int ChurnedCount)> GetPagedAsync( string? searchTerm, string sortColumn, string sortDirection, int page, int pageSize, bool hideChurned = true) { var cutoff = DateTime.UtcNow.AddDays(-14); // Always count churned regardless of hideChurned so the banner can show a number. var churnedCount = await _context.Companies .AsNoTracking() .IgnoreQueryFilters() .Where(c => !c.IsDeleted && (c.SubscriptionStatus == SubscriptionStatus.Expired || c.SubscriptionStatus == SubscriptionStatus.Canceled) && c.SubscriptionEndDate != null && c.SubscriptionEndDate < cutoff) .CountAsync(); var query = _context.Companies .AsNoTracking() .IgnoreQueryFilters() .Where(c => !c.IsDeleted) .AsQueryable(); if (hideChurned) query = query.Where(c => !((c.SubscriptionStatus == SubscriptionStatus.Expired || c.SubscriptionStatus == SubscriptionStatus.Canceled) && c.SubscriptionEndDate != null && c.SubscriptionEndDate < cutoff)); if (!string.IsNullOrWhiteSpace(searchTerm)) { var s = searchTerm.ToLower(); query = query.Where(c => c.CompanyName.ToLower().Contains(s) || (c.CompanyCode != null && c.CompanyCode.ToLower().Contains(s)) || (c.PrimaryContactEmail != null && c.PrimaryContactEmail.ToLower().Contains(s)) || (c.Phone != null && c.Phone.ToLower().Contains(s))); } query = (sortColumn, sortDirection == "asc") switch { ("CompanyName", true) => query.OrderBy(c => c.CompanyName), ("CompanyName", false) => query.OrderByDescending(c => c.CompanyName), ("Plan", true) => query.OrderBy(c => c.SubscriptionPlan), ("Plan", false) => query.OrderByDescending(c => c.SubscriptionPlan), ("Status", true) => query.OrderBy(c => c.IsActive), ("Status", false) => query.OrderByDescending(c => c.IsActive), ("Created", true) => query.OrderBy(c => c.CreatedAt), ("Created", false) => query.OrderByDescending(c => c.CreatedAt), _ => query.OrderBy(c => c.CompanyName) }; var totalCount = await query.CountAsync(); var companies = await query .Include(c => c.Users) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return (companies, totalCount, churnedCount); } /// public async Task GetCountSummaryAsync(IReadOnlyList companyIds) { var now = DateTime.UtcNow; var d30 = now.AddDays(-30); var d90 = now.AddDays(-90); var jobCounts = await _context.Jobs .IgnoreQueryFilters() .Where(j => companyIds.Contains(j.CompanyId) && !j.IsDeleted) .GroupBy(j => j.CompanyId) .Select(g => new { g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.Key, x => x.Count); var quoteCounts = await _context.Quotes .IgnoreQueryFilters() .Where(q => companyIds.Contains(q.CompanyId) && !q.IsDeleted) .GroupBy(q => q.CompanyId) .Select(g => new { g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.Key, x => x.Count); var customerCounts = await _context.Customers .IgnoreQueryFilters() .Where(c => companyIds.Contains(c.CompanyId) && !c.IsDeleted) .GroupBy(c => c.CompanyId) .Select(g => new { g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.Key, x => x.Count); var wizardRaw = await _context.CompanyPreferences .IgnoreQueryFilters() .Where(p => companyIds.Contains(p.CompanyId) && p.SetupWizardCompleted) .Select(p => new { p.CompanyId, p.SetupWizardCompletedAt, p.SetupWizardCompletedByName }) .ToListAsync(); var wizardInfo = wizardRaw.ToDictionary( x => x.CompanyId, x => new CompanyWizardInfo(true, x.SetupWizardCompletedAt, x.SetupWizardCompletedByName)); var jobs30 = await _context.Jobs .IgnoreQueryFilters() .Where(j => companyIds.Contains(j.CompanyId) && !j.IsDeleted && j.CreatedAt >= d30) .GroupBy(j => j.CompanyId) .Select(g => new { g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.Key, x => x.Count); var jobs90 = await _context.Jobs .IgnoreQueryFilters() .Where(j => companyIds.Contains(j.CompanyId) && !j.IsDeleted && j.CreatedAt >= d90) .GroupBy(j => j.CompanyId) .Select(g => new { g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.Key, x => x.Count); var lastLoginRaw = await _context.Users .IgnoreQueryFilters() .Where(u => companyIds.Contains(u.CompanyId) && u.LastLoginDate != null) .GroupBy(u => u.CompanyId) .Select(g => new { CompanyId = g.Key, Last = g.Max(u => u.LastLoginDate) }) .ToListAsync(); var lastLogins = lastLoginRaw.ToDictionary( x => x.CompanyId, x => x.Last); return new CompanyCountSummary(jobCounts, quoteCounts, customerCounts, wizardInfo, jobs30, jobs90, lastLogins); } }