Phase 1: Introduce typed repository interfaces and report service stubs
Six IUnitOfWork properties upgraded from generic IRepository<T> to domain-specific typed interfaces (IJobRepository, IQuoteRepository, IInvoiceRepository, ICustomerRepository, IBillRepository, IPurchaseOrderRepository). Each backed by a concrete typed repository that encapsulates complex include chains previously inlined in controllers. Also adds IFinancialReportService and IOperationalReportService stub implementations (NotImplementedException placeholders) to Application.Interfaces and Infrastructure.Services, registered in Program.cs. These are the migration targets for ReportsController's aggregate query methods in Phase 2. No controller behaviour changed in this commit — all callers still compile because typed interfaces extend IRepository<T>. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Interfaces.Repositories;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
|
||||
namespace PowderCoating.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Typed repository for <see cref="Job"/> that provides domain-specific multi-level
|
||||
/// include queries that the generic <see cref="Repository{T}"/> cannot express.
|
||||
/// The base class handles all standard CRUD operations; this class adds read queries
|
||||
/// that were previously scattered as inline EF expressions inside controllers.
|
||||
/// </summary>
|
||||
public class JobRepository : Repository<Job>, IJobRepository
|
||||
{
|
||||
public JobRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<Job>> GetBoardJobsAsync()
|
||||
{
|
||||
return await _context.Jobs
|
||||
.AsNoTracking()
|
||||
.Where(j => !j.IsDeleted)
|
||||
.Include(j => j.Customer)
|
||||
.Include(j => j.JobStatus)
|
||||
.Include(j => j.JobPriority)
|
||||
.Include(j => j.AssignedUser)
|
||||
.OrderBy(j => j.DueDate.HasValue ? 0 : 1)
|
||||
.ThenBy(j => j.DueDate)
|
||||
.ThenBy(j => j.JobPriority!.DisplayOrder)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Job?> LoadForDetailsAsync(int id)
|
||||
{
|
||||
// Single query replaces the per-item N+1 loop that was in JobsController.Details.
|
||||
// EF Core splits the multi-level ThenIncludes across two SQL queries automatically
|
||||
// (split query behavior), keeping result set size manageable.
|
||||
return await _context.Jobs
|
||||
.Where(j => j.Id == id && !j.IsDeleted)
|
||||
.Include(j => j.Customer)
|
||||
.Include(j => j.JobStatus)
|
||||
.Include(j => j.JobPriority)
|
||||
.Include(j => j.AssignedUser)
|
||||
.Include(j => j.Quote)
|
||||
.Include(j => j.OvenCost)
|
||||
.Include(j => j.OriginalJob)
|
||||
.Include(j => j.IntakeCheckedBy)
|
||||
.Include(j => j.JobItems.Where(ji => !ji.IsDeleted))
|
||||
.ThenInclude(ji => ji.Coats)
|
||||
.ThenInclude(c => c.InventoryItem)
|
||||
.Include(j => j.JobItems.Where(ji => !ji.IsDeleted))
|
||||
.ThenInclude(ji => ji.Coats)
|
||||
.ThenInclude(c => c.Vendor)
|
||||
.Include(j => j.JobItems.Where(ji => !ji.IsDeleted))
|
||||
.ThenInclude(ji => ji.PrepServices)
|
||||
.ThenInclude(ps => ps.PrepService)
|
||||
.Include(j => j.JobPrepServices.Where(jps => !jps.IsDeleted))
|
||||
.ThenInclude(jps => jps.PrepService)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Job?> LoadForEditAsync(int id)
|
||||
{
|
||||
return await _context.Jobs
|
||||
.Where(j => j.Id == id && !j.IsDeleted)
|
||||
.Include(j => j.Customer)
|
||||
.Include(j => j.JobStatus)
|
||||
.Include(j => j.JobPriority)
|
||||
.Include(j => j.AssignedUser)
|
||||
.Include(j => j.OvenCost)
|
||||
.Include(j => j.JobItems.Where(ji => !ji.IsDeleted))
|
||||
.ThenInclude(ji => ji.Coats)
|
||||
.ThenInclude(c => c.InventoryItem)
|
||||
.Include(j => j.JobItems.Where(ji => !ji.IsDeleted))
|
||||
.ThenInclude(ji => ji.Coats)
|
||||
.ThenInclude(c => c.Vendor)
|
||||
.Include(j => j.JobItems.Where(ji => !ji.IsDeleted))
|
||||
.ThenInclude(ji => ji.PrepServices)
|
||||
.ThenInclude(ps => ps.PrepService)
|
||||
.Include(j => j.JobPrepServices.Where(jps => !jps.IsDeleted))
|
||||
.ThenInclude(jps => jps.PrepService)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Job?> LoadForStatusChangeAsync(int id)
|
||||
{
|
||||
return await _context.Jobs
|
||||
.Include(j => j.JobStatus)
|
||||
.FirstOrDefaultAsync(j => j.Id == id && !j.IsDeleted);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<JobChangeHistory>> GetChangeHistoryAsync(int jobId)
|
||||
{
|
||||
return await _context.JobChangeHistories
|
||||
.Where(h => h.JobId == jobId && !h.IsDeleted)
|
||||
.Include(h => h.ChangedBy)
|
||||
.OrderByDescending(h => h.ChangedAt)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user