using Microsoft.EntityFrameworkCore; using PowderCoating.Core.Entities; using PowderCoating.Core.Interfaces.Repositories; using PowderCoating.Infrastructure.Data; namespace PowderCoating.Infrastructure.Repositories; /// /// Typed repository for that provides domain-specific queries previously /// scattered as inline EF expressions across QuotesController and /// QuoteApprovalController. /// public class QuoteRepository : Repository, IQuoteRepository { public QuoteRepository(ApplicationDbContext context) : base(context) { } /// public async Task LoadForDetailsAsync(int id) { var quote = await _context.Quotes .Where(q => q.Id == id && !q.IsDeleted) .Include(q => q.Customer) .Include(q => q.PreparedBy) .Include(q => q.QuoteStatus) .Include(q => q.OvenCost) .Include(q => q.QuotePrepServices.Where(qps => !qps.IsDeleted)) .ThenInclude(qps => qps.PrepService) .FirstOrDefaultAsync(); if (quote == null) return null; // QuoteItems with nested coats and prep services loaded separately to avoid // cartesian explosion from multiple collection includes in a single query. quote.QuoteItems = await _context.QuoteItems .Where(qi => qi.QuoteId == id && !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) .ToListAsync(); return quote; } /// public async Task GetByApprovalTokenAsync(string token) { // IgnoreQueryFilters: approval portal is unauthenticated — no tenant context on the request. return await _context.Quotes .IgnoreQueryFilters() .Include(q => q.Customer) .Include(q => q.QuoteStatus) .Include(q => q.QuoteItems.Where(qi => !qi.IsDeleted)) .FirstOrDefaultAsync(q => q.ApprovalToken == token && !q.IsDeleted); } /// public async Task> GetChangeHistoryAsync(int quoteId) { return await _context.QuoteChangeHistories .Where(h => h.QuoteId == quoteId && !h.IsDeleted) .Include(h => h.ChangedBy) .OrderByDescending(h => h.ChangedAt) .AsNoTracking() .ToListAsync(); } /// public async Task GetIndexStatsAsync(List openStatusIds, List 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)); } /// public async Task> 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(); } /// public async Task 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(); } }