Phase 2: Eliminate ApplicationDbContext from domain controllers

Migrated InvoicesController, QuotesController, JobsController, BillsController,
PurchaseOrdersController, and CustomersController to route all data access
through IUnitOfWork typed/generic repositories instead of injecting
ApplicationDbContext directly.

New typed repositories added: IJobRepository (GetScheduledJobsForDateAsync,
GetActiveJobsForMobileAsync, LoadForCostingAsync), INotificationLogRepository
(GetLatestForJobAsync, GetAllForJobAsync), IQuoteRepository (GetItemsWithCoatsAsync
with CatalogItem eager load + AsNoTracking), and IJobRepository.GetOrphanedConversionJobAsync.

All EF complex include chains relocated into repository methods; controllers now
call named query methods rather than composing raw IQueryable chains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 21:20:39 -04:00
parent 80b0e547cc
commit 90bc0d965f
20 changed files with 730 additions and 878 deletions
@@ -67,4 +67,45 @@ public class QuoteRepository : Repository<Quote>, IQuoteRepository
.AsNoTracking()
.ToListAsync();
}
/// <inheritdoc/>
public async Task<QuoteIndexStats> GetIndexStatsAsync(List<int> openStatusIds, List<int> approvedConvertedStatusIds)
{
var stats = await _context.Quotes
.Where(q => !q.IsDeleted)
.Select(q => new { q.QuoteStatusId, q.Total })
.ToListAsync();
return new QuoteIndexStats(
OpenCount: stats.Count(q => openStatusIds.Contains(q.QuoteStatusId)),
ApprovedConvertedCount: stats.Count(q => approvedConvertedStatusIds.Contains(q.QuoteStatusId)),
TotalValue: stats.Sum(q => q.Total));
}
/// <inheritdoc/>
public async Task<List<QuoteItem>> GetItemsWithCoatsAsync(int quoteId)
{
return await _context.QuoteItems
.Where(qi => qi.QuoteId == quoteId && !qi.IsDeleted)
.Include(qi => qi.Coats)
.ThenInclude(c => c.InventoryItem)
.Include(qi => qi.Coats)
.ThenInclude(c => c.Vendor)
.Include(qi => qi.CatalogItem)
.Include(qi => qi.PrepServices)
.ThenInclude(ps => ps.PrepService)
.AsNoTracking()
.ToListAsync();
}
/// <inheritdoc/>
public async Task<string?> GetLastQuoteNumberByPrefixAsync(int companyId, string prefix)
{
return await _context.Quotes
.IgnoreQueryFilters()
.Where(q => q.CompanyId == companyId && q.QuoteNumber.StartsWith(prefix))
.OrderByDescending(q => q.QuoteNumber)
.Select(q => q.QuoteNumber)
.FirstOrDefaultAsync();
}
}