Files
PowderCoatingLogix/src/PowderCoating.Infrastructure/Repositories/BillRepository.cs
T
spouliot 90bc0d965f 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>
2026-04-27 21:20:39 -04:00

93 lines
3.5 KiB
C#

using Microsoft.EntityFrameworkCore;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;
using PowderCoating.Core.Interfaces.Repositories;
using PowderCoating.Infrastructure.Data;
namespace PowderCoating.Infrastructure.Repositories;
/// <summary>
/// Typed repository for <see cref="Bill"/> that provides domain-specific multi-level
/// include queries previously expressed inline in <c>BillsController</c>.
/// </summary>
public class BillRepository : Repository<Bill>, IBillRepository
{
public BillRepository(ApplicationDbContext context) : base(context) { }
/// <inheritdoc/>
public async Task<Bill?> LoadForViewAsync(int id)
{
return await _context.Bills
.Where(b => b.Id == id && !b.IsDeleted)
.Include(b => b.Vendor)
.Include(b => b.APAccount)
.Include(b => b.LineItems.Where(li => !li.IsDeleted))
.ThenInclude(li => li.Account)
.Include(b => b.LineItems.Where(li => !li.IsDeleted))
.ThenInclude(li => li.Job)
.Include(b => b.Payments.Where(p => !p.IsDeleted))
.ThenInclude(p => p.BankAccount)
.FirstOrDefaultAsync();
}
/// <inheritdoc/>
public async Task<Bill?> LoadForEditAsync(int id)
{
return await _context.Bills
.Where(b => b.Id == id && !b.IsDeleted)
.Include(b => b.LineItems.Where(li => !li.IsDeleted))
.FirstOrDefaultAsync();
}
/// <inheritdoc/>
public async Task<List<Bill>> GetForIndexAsync(string? statusFilter, string? searchTerm, decimal? searchAmount)
{
var query = _context.Bills
.Include(b => b.Vendor)
.Include(b => b.LineItems.Where(li => !li.IsDeleted))
.Where(b => !b.IsDeleted);
if (statusFilter == "Unpaid")
query = query.Where(b => b.Status == BillStatus.Open || b.Status == BillStatus.PartiallyPaid);
else if (statusFilter == "Overdue")
query = query.Where(b => b.Status != BillStatus.Paid && b.Status != BillStatus.Voided &&
b.DueDate.HasValue && b.DueDate.Value.Date < DateTime.Today);
if (!string.IsNullOrEmpty(searchTerm))
{
var term = searchTerm;
query = query.Where(b =>
b.BillNumber.Contains(term) ||
b.Vendor.CompanyName.Contains(term) ||
(b.VendorInvoiceNumber != null && b.VendorInvoiceNumber.Contains(term)) ||
(b.Memo != null && b.Memo.Contains(term)) ||
b.LineItems.Any(li => li.Description.Contains(term)) ||
(searchAmount.HasValue && (b.Total == searchAmount.Value || b.AmountPaid == searchAmount.Value)));
}
return await query.OrderByDescending(b => b.BillDate).ToListAsync();
}
/// <inheritdoc/>
public async Task<string?> GetLastBillNumberAsync(string prefix)
{
return await _context.Bills
.IgnoreQueryFilters()
.Where(b => b.BillNumber.StartsWith(prefix))
.OrderByDescending(b => b.BillNumber)
.Select(b => b.BillNumber)
.FirstOrDefaultAsync();
}
/// <inheritdoc/>
public async Task<string?> GetLastPaymentNumberAsync(string prefix)
{
return await _context.BillPayments
.IgnoreQueryFilters()
.Where(p => p.PaymentNumber.StartsWith(prefix))
.OrderByDescending(p => p.PaymentNumber)
.Select(p => p.PaymentNumber)
.FirstOrDefaultAsync();
}
}