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:
@@ -3,14 +3,13 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PowderCoating.Application.DTOs.Common;
|
||||
using PowderCoating.Application.DTOs.PurchaseOrder;
|
||||
using PowderCoating.Application.Interfaces;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Enums;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
using PowderCoating.Core.Interfaces.Repositories;
|
||||
using PowderCoating.Shared.Constants;
|
||||
using PowderCoating.Web.Helpers;
|
||||
|
||||
@@ -23,7 +22,6 @@ public class PurchaseOrdersController : Controller
|
||||
private readonly IMapper _mapper;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ILogger<PurchaseOrdersController> _logger;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IPdfService _pdfService;
|
||||
|
||||
public PurchaseOrdersController(
|
||||
@@ -31,14 +29,12 @@ public class PurchaseOrdersController : Controller
|
||||
IMapper mapper,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
ILogger<PurchaseOrdersController> logger,
|
||||
ApplicationDbContext context,
|
||||
IPdfService pdfService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
_pdfService = pdfService;
|
||||
}
|
||||
|
||||
@@ -69,55 +65,10 @@ public class PurchaseOrdersController : Controller
|
||||
pageSize = Math.Clamp(pageSize, 10, 100);
|
||||
pageNumber = Math.Max(1, pageNumber);
|
||||
|
||||
var query = _context.Set<PurchaseOrder>()
|
||||
.Include(po => po.Vendor)
|
||||
.Include(po => po.Items.Where(i => !i.IsDeleted))
|
||||
.Where(po => !po.IsDeleted && po.CompanyId == currentUser.CompanyId)
|
||||
.AsQueryable();
|
||||
|
||||
if (statusFilter.HasValue)
|
||||
query = query.Where(po => po.Status == statusFilter.Value);
|
||||
|
||||
if (vendorId.HasValue)
|
||||
query = query.Where(po => po.VendorId == vendorId.Value);
|
||||
|
||||
if (dateFrom.HasValue)
|
||||
query = query.Where(po => po.OrderDate >= dateFrom.Value);
|
||||
|
||||
if (dateTo.HasValue)
|
||||
query = query.Where(po => po.OrderDate <= dateTo.Value.AddDays(1));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
var term = searchTerm.Trim().ToLower();
|
||||
query = query.Where(po =>
|
||||
po.PoNumber.ToLower().Contains(term) ||
|
||||
po.Vendor.CompanyName.ToLower().Contains(term) ||
|
||||
(po.Notes != null && po.Notes.ToLower().Contains(term)));
|
||||
}
|
||||
|
||||
query = (sortColumn?.ToLower(), sortDirection?.ToLower()) switch
|
||||
{
|
||||
("ponumber", "asc") => query.OrderBy(po => po.PoNumber),
|
||||
("ponumber", _) => query.OrderByDescending(po => po.PoNumber),
|
||||
("vendor", "asc") => query.OrderBy(po => po.Vendor.CompanyName),
|
||||
("vendor", _) => query.OrderByDescending(po => po.Vendor.CompanyName),
|
||||
("status", "asc") => query.OrderBy(po => po.Status),
|
||||
("status", _) => query.OrderByDescending(po => po.Status),
|
||||
("orderdate", "asc") => query.OrderBy(po => po.OrderDate),
|
||||
("orderdate", _) => query.OrderByDescending(po => po.OrderDate),
|
||||
("expected", "asc") => query.OrderBy(po => po.ExpectedDeliveryDate),
|
||||
("expected", _) => query.OrderByDescending(po => po.ExpectedDeliveryDate),
|
||||
("total", "asc") => query.OrderBy(po => po.TotalAmount),
|
||||
("total", _) => query.OrderByDescending(po => po.TotalAmount),
|
||||
_ => query.OrderByDescending(po => po.OrderDate)
|
||||
};
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
var (items, totalCount) = await _unitOfWork.PurchaseOrders.GetPagedAsync(
|
||||
currentUser.CompanyId, pageNumber, pageSize,
|
||||
statusFilter, vendorId, dateFrom, dateTo,
|
||||
searchTerm, sortColumn, sortDirection);
|
||||
|
||||
var dtos = _mapper.Map<List<PurchaseOrderListDto>>(items);
|
||||
|
||||
@@ -129,24 +80,12 @@ public class PurchaseOrdersController : Controller
|
||||
PageSize = pageSize
|
||||
};
|
||||
|
||||
// Stats
|
||||
var allForStats = await _context.Set<PurchaseOrder>()
|
||||
.Where(po => !po.IsDeleted && po.CompanyId == currentUser.CompanyId)
|
||||
.Select(po => new { po.Status, po.TotalAmount, po.ExpectedDeliveryDate })
|
||||
.ToListAsync();
|
||||
|
||||
ViewBag.TotalCount = allForStats.Count;
|
||||
ViewBag.OpenCount = allForStats.Count(p =>
|
||||
p.Status == PurchaseOrderStatus.Draft ||
|
||||
p.Status == PurchaseOrderStatus.Submitted ||
|
||||
p.Status == PurchaseOrderStatus.PartiallyReceived);
|
||||
ViewBag.CommittedValue = allForStats
|
||||
.Where(p => p.Status != PurchaseOrderStatus.Cancelled)
|
||||
.Sum(p => p.TotalAmount);
|
||||
ViewBag.OverdueCount = allForStats.Count(p =>
|
||||
(p.Status == PurchaseOrderStatus.Draft || p.Status == PurchaseOrderStatus.Submitted || p.Status == PurchaseOrderStatus.PartiallyReceived)
|
||||
&& p.ExpectedDeliveryDate.HasValue
|
||||
&& p.ExpectedDeliveryDate.Value.Date < DateTime.UtcNow.Date);
|
||||
// Stats (server-side projection — only three columns fetched)
|
||||
var stats = await _unitOfWork.PurchaseOrders.GetStatsAsync(currentUser.CompanyId);
|
||||
ViewBag.TotalCount = stats.TotalCount;
|
||||
ViewBag.OpenCount = stats.OpenCount;
|
||||
ViewBag.CommittedValue = stats.CommittedValue;
|
||||
ViewBag.OverdueCount = stats.OverdueCount;
|
||||
|
||||
await PopulateVendorFilterDropdownAsync(currentUser.CompanyId);
|
||||
ViewBag.SearchTerm = searchTerm;
|
||||
@@ -172,13 +111,7 @@ public class PurchaseOrdersController : Controller
|
||||
var currentUser = await _userManager.GetUserAsync(User);
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Vendor)
|
||||
.Include(p => p.Bill)
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.InventoryItem)
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
if (po == null) return NotFound();
|
||||
|
||||
var dto = _mapper.Map<PurchaseOrderDto>(po);
|
||||
@@ -283,10 +216,7 @@ public class PurchaseOrdersController : Controller
|
||||
var currentUser = await _userManager.GetUserAsync(User);
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
if (po == null) return NotFound();
|
||||
if (po.Status != PurchaseOrderStatus.Draft)
|
||||
{
|
||||
@@ -335,10 +265,7 @@ public class PurchaseOrdersController : Controller
|
||||
var currentUser = await _userManager.GetUserAsync(User);
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
if (po == null) return NotFound();
|
||||
if (po.Status != PurchaseOrderStatus.Draft)
|
||||
{
|
||||
@@ -416,10 +343,7 @@ public class PurchaseOrdersController : Controller
|
||||
var currentUser = await _userManager.GetUserAsync(User);
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Vendor)
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
if (po == null) return NotFound();
|
||||
|
||||
if (po.Status != PurchaseOrderStatus.Draft && po.Status != PurchaseOrderStatus.Cancelled)
|
||||
@@ -487,10 +411,7 @@ public class PurchaseOrdersController : Controller
|
||||
var currentUser = await _userManager.GetUserAsync(User);
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
if (po == null) return NotFound();
|
||||
|
||||
if (po.Status != PurchaseOrderStatus.Draft)
|
||||
@@ -560,11 +481,7 @@ public class PurchaseOrdersController : Controller
|
||||
var currentUser = await _userManager.GetUserAsync(User);
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Vendor)
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.InventoryItem)
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
|
||||
if (po == null) return NotFound();
|
||||
|
||||
@@ -618,11 +535,7 @@ public class PurchaseOrdersController : Controller
|
||||
var currentUser = await _userManager.GetUserAsync(User);
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Vendor)
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.InventoryItem)
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
|
||||
if (po == null) return NotFound();
|
||||
|
||||
@@ -692,7 +605,7 @@ public class PurchaseOrdersController : Controller
|
||||
CompanyId = po.CompanyId
|
||||
};
|
||||
|
||||
await _context.Set<InventoryTransaction>().AddAsync(transaction);
|
||||
await _unitOfWork.InventoryTransactions.AddAsync(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,9 +633,8 @@ public class PurchaseOrdersController : Controller
|
||||
}
|
||||
|
||||
po.UpdatedAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
}); // end ExecuteInTransactionAsync
|
||||
}); // end ExecuteInTransactionAsync — SaveChangesAsync called automatically before commit
|
||||
|
||||
this.ToastSuccess(allReceived
|
||||
? $"All items received for {po.PoNumber}."
|
||||
@@ -754,11 +666,7 @@ public class PurchaseOrdersController : Controller
|
||||
|
||||
try
|
||||
{
|
||||
var po = await _context.Set<PurchaseOrder>()
|
||||
.Include(p => p.Vendor)
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.InventoryItem)
|
||||
.FirstOrDefaultAsync(p => p.Id == id && !p.IsDeleted && p.CompanyId == currentUser.CompanyId);
|
||||
var po = await _unitOfWork.PurchaseOrders.LoadForViewAsync(id, currentUser.CompanyId);
|
||||
|
||||
if (po == null) return NotFound();
|
||||
|
||||
@@ -806,14 +714,12 @@ public class PurchaseOrdersController : Controller
|
||||
if (currentUser == null) return Unauthorized();
|
||||
|
||||
// Find low-stock items that have a primary vendor
|
||||
var lowStockItems = await _context.Set<InventoryItem>()
|
||||
.Include(i => i.PrimaryVendor)
|
||||
.Where(i => !i.IsDeleted
|
||||
&& i.IsActive
|
||||
&& i.CompanyId == currentUser.CompanyId
|
||||
&& i.PrimaryVendorId != null
|
||||
&& i.QuantityOnHand <= i.ReorderPoint)
|
||||
.ToListAsync();
|
||||
var lowStockItems = (await _unitOfWork.InventoryItems.FindAsync(
|
||||
i => i.IsActive && i.CompanyId == currentUser.CompanyId &&
|
||||
i.PrimaryVendorId != null && i.QuantityOnHand <= i.ReorderPoint,
|
||||
false,
|
||||
i => i.PrimaryVendor!))
|
||||
.ToList();
|
||||
|
||||
if (!lowStockItems.Any())
|
||||
{
|
||||
@@ -878,11 +784,10 @@ public class PurchaseOrdersController : Controller
|
||||
{
|
||||
var prefix = $"PO-{DateTime.UtcNow:yy}{DateTime.UtcNow.Month:D2}-";
|
||||
|
||||
var existing = await _context.Set<PurchaseOrder>()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(po => po.CompanyId == companyId && po.PoNumber.StartsWith(prefix))
|
||||
.Select(po => po.PoNumber)
|
||||
.ToListAsync();
|
||||
var existingPos = await _unitOfWork.PurchaseOrders.FindAsync(
|
||||
po => po.CompanyId == companyId && po.PoNumber.StartsWith(prefix),
|
||||
ignoreQueryFilters: true);
|
||||
var existing = existingPos.Select(po => po.PoNumber).ToList();
|
||||
|
||||
var maxNum = 0;
|
||||
foreach (var num in existing)
|
||||
@@ -903,17 +808,18 @@ public class PurchaseOrdersController : Controller
|
||||
/// </summary>
|
||||
private async Task PopulateCreateViewBagAsync(int companyId)
|
||||
{
|
||||
var vendors = await _context.Set<Vendor>()
|
||||
.Where(v => !v.IsDeleted && v.CompanyId == companyId && v.IsActive)
|
||||
var vendorEntities = await _unitOfWork.Vendors.FindAsync(
|
||||
v => v.CompanyId == companyId && v.IsActive);
|
||||
var vendors = vendorEntities
|
||||
.OrderBy(v => v.CompanyName)
|
||||
.Select(v => new SelectListItem(v.CompanyName, v.Id.ToString()))
|
||||
.ToListAsync();
|
||||
|
||||
.ToList();
|
||||
vendors.Insert(0, new SelectListItem("— Select Vendor —", ""));
|
||||
ViewBag.Vendors = vendors;
|
||||
|
||||
var inventoryItems = await _context.Set<InventoryItem>()
|
||||
.Where(i => !i.IsDeleted && i.CompanyId == companyId && i.IsActive)
|
||||
var inventoryEntities = await _unitOfWork.InventoryItems.FindAsync(
|
||||
i => i.CompanyId == companyId && i.IsActive);
|
||||
var inventoryItems = inventoryEntities
|
||||
.OrderBy(i => i.Name)
|
||||
.Select(i => new
|
||||
{
|
||||
@@ -922,8 +828,7 @@ public class PurchaseOrdersController : Controller
|
||||
uom = i.UnitOfMeasure ?? "units",
|
||||
cost = i.LastPurchasePrice > 0 ? i.LastPurchasePrice : i.UnitCost
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
.ToList();
|
||||
ViewBag.InventoryItemsJson = System.Text.Json.JsonSerializer.Serialize(inventoryItems);
|
||||
}
|
||||
|
||||
@@ -934,12 +839,11 @@ public class PurchaseOrdersController : Controller
|
||||
/// </summary>
|
||||
private async Task PopulateVendorFilterDropdownAsync(int companyId)
|
||||
{
|
||||
var vendors = await _context.Set<Vendor>()
|
||||
.Where(v => !v.IsDeleted && v.CompanyId == companyId)
|
||||
var vendorEntities = await _unitOfWork.Vendors.FindAsync(v => v.CompanyId == companyId);
|
||||
var vendors = vendorEntities
|
||||
.OrderBy(v => v.CompanyName)
|
||||
.Select(v => new SelectListItem(v.CompanyName, v.Id.ToString()))
|
||||
.ToListAsync();
|
||||
|
||||
.ToList();
|
||||
vendors.Insert(0, new SelectListItem("All Vendors", ""));
|
||||
ViewBag.VendorList = vendors;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user