1a44133a63
Removes the ShopWorker and ShopWorkerRoleCost entities, all related DTOs, mappings, controllers, views, and import/export paths. Worker identity is now handled entirely through ApplicationUser with per-user LaborCostPerHour. ShopWorkerRoleCosts table remains in production pending manual data migration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
208 lines
8.0 KiB
C#
208 lines
8.0 KiB
C#
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();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<string?> GetLastJobNumberByPrefixAsync(int companyId, string prefix)
|
|
{
|
|
return await _context.Jobs
|
|
.IgnoreQueryFilters()
|
|
.Where(j => j.CompanyId == companyId && j.JobNumber.StartsWith(prefix))
|
|
.OrderByDescending(j => j.JobNumber)
|
|
.Select(j => j.JobNumber)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<Job?> GetOrphanedConversionJobAsync(int quoteId, int companyId)
|
|
{
|
|
return await _context.Jobs
|
|
.IgnoreQueryFilters()
|
|
.FirstOrDefaultAsync(j => j.QuoteId == quoteId && j.CompanyId == companyId);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<List<Job>> GetScheduledJobsForDateAsync(DateTime date, string? userId = null)
|
|
{
|
|
var query = _context.Jobs
|
|
.Include(j => j.Customer)
|
|
.Include(j => j.JobStatus)
|
|
.Include(j => j.JobPriority)
|
|
.Include(j => j.AssignedUser)
|
|
.Include(j => j.JobItems.Where(i => !i.IsDeleted))
|
|
.ThenInclude(i => i.Coats)
|
|
.Where(j => j.ScheduledDate.HasValue && j.ScheduledDate.Value.Date == date.Date
|
|
&& !j.IsDeleted && !j.JobStatus.IsTerminalStatus);
|
|
|
|
if (!string.IsNullOrEmpty(userId))
|
|
query = query.Where(j => j.AssignedUserId == userId);
|
|
|
|
return await query.ToListAsync();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<List<Job>> GetActiveJobsForMobileAsync(int companyId, string? workerId = null)
|
|
{
|
|
var query = _context.Jobs
|
|
.Include(j => j.Customer)
|
|
.Include(j => j.JobStatus)
|
|
.Include(j => j.JobPriority)
|
|
.Include(j => j.AssignedUser)
|
|
.Include(j => j.JobItems.Where(i => !i.IsDeleted))
|
|
.ThenInclude(i => i.Coats)
|
|
.Where(j => j.CompanyId == companyId && !j.IsDeleted && !j.JobStatus.IsTerminalStatus
|
|
&& j.JobStatus.StatusCode != "ON_HOLD" && j.JobStatus.StatusCode != "CANCELLED");
|
|
|
|
if (!string.IsNullOrEmpty(workerId))
|
|
query = query.Where(j => j.AssignedUserId == workerId);
|
|
|
|
return await query.ToListAsync();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<Job?> LoadForCostingAsync(int jobId, int companyId)
|
|
{
|
|
return await _context.Jobs
|
|
.Where(j => j.Id == jobId && j.CompanyId == companyId && !j.IsDeleted)
|
|
.Include(j => j.OvenCost)
|
|
.Include(j => j.Invoice)
|
|
.Include(j => j.JobItems.Where(i => !i.IsDeleted))
|
|
.ThenInclude(i => i.Coats.Where(c => !c.IsDeleted))
|
|
.Include(j => j.TimeEntries.Where(t => !t.IsDeleted))
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<Job?> LoadForTemplateSnapshotAsync(int jobId)
|
|
{
|
|
return await _context.Jobs
|
|
.Where(j => j.Id == jobId && !j.IsDeleted)
|
|
.Include(j => j.JobItems.Where(i => !i.IsDeleted))
|
|
.ThenInclude(i => i.Coats.Where(c => !c.IsDeleted))
|
|
.Include(j => j.JobItems.Where(i => !i.IsDeleted))
|
|
.ThenInclude(i => i.PrepServices.Where(p => !p.IsDeleted))
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<List<Job>> GetOverdueScheduledJobsAsync()
|
|
{
|
|
var today = DateTime.Today;
|
|
return await _context.Jobs
|
|
.Include(j => j.Customer)
|
|
.Include(j => j.JobStatus)
|
|
.Include(j => j.JobPriority)
|
|
.Include(j => j.AssignedUser)
|
|
.Where(j => j.ScheduledDate.HasValue
|
|
&& j.ScheduledDate.Value.Date < today
|
|
&& !j.IsDeleted
|
|
&& !j.JobStatus.IsTerminalStatus)
|
|
.OrderBy(j => j.ScheduledDate)
|
|
.ThenBy(j => j.JobNumber)
|
|
.ToListAsync();
|
|
}
|
|
}
|