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,40 @@
|
||||
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="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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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="Customer"/> that adds domain-specific queries on top of
|
||||
/// the generic CRUD interface.
|
||||
/// </summary>
|
||||
public class CustomerRepository : Repository<Customer>, ICustomerRepository
|
||||
{
|
||||
public CustomerRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Customer?> LoadForDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Customers
|
||||
.Where(c => c.Id == id && !c.IsDeleted)
|
||||
.Include(c => c.PricingTier)
|
||||
.Include(c => c.CustomerNotes.Where(n => !n.IsDeleted))
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Customer?> FindByEmailAsync(string email)
|
||||
{
|
||||
return await _context.Customers
|
||||
.FirstOrDefaultAsync(c => c.Email == email && !c.IsDeleted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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="Invoice"/> that provides domain-specific multi-level
|
||||
/// include queries previously expressed inline in <c>InvoicesController.LoadInvoiceForViewAsync</c>.
|
||||
/// </summary>
|
||||
public class InvoiceRepository : Repository<Invoice>, IInvoiceRepository
|
||||
{
|
||||
public InvoiceRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Invoice?> LoadForViewAsync(int id)
|
||||
{
|
||||
return await _context.Set<Invoice>()
|
||||
.Where(i => i.Id == id && !i.IsDeleted)
|
||||
.Include(i => i.Customer)
|
||||
.Include(i => i.Job)
|
||||
.Include(i => i.PreparedBy)
|
||||
.Include(i => i.SalesTaxAccount)
|
||||
.Include(i => i.InvoiceItems.Where(ii => !ii.IsDeleted))
|
||||
.ThenInclude(ii => ii.RevenueAccount)
|
||||
.Include(i => i.InvoiceItems.Where(ii => !ii.IsDeleted))
|
||||
.ThenInclude(ii => ii.GeneratedGiftCertificate)
|
||||
.Include(i => i.Payments.Where(p => !p.IsDeleted))
|
||||
.ThenInclude(p => p.RecordedBy)
|
||||
.Include(i => i.Payments.Where(p => !p.IsDeleted))
|
||||
.ThenInclude(p => p.DepositAccount)
|
||||
.Include(i => i.Refunds.Where(r => !r.IsDeleted))
|
||||
.ThenInclude(r => r.IssuedBy)
|
||||
.Include(i => i.CreditApplications.Where(ca => !ca.IsDeleted))
|
||||
.ThenInclude(ca => ca.CreditMemo)
|
||||
.Include(i => i.GiftCertificateRedemptions)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Invoice?> GetForJobAsync(int jobId, bool includeDeleted = false)
|
||||
{
|
||||
var query = _context.Set<Invoice>().Where(i => i.JobId == jobId);
|
||||
if (!includeDeleted)
|
||||
query = query.Where(i => !i.IsDeleted);
|
||||
else
|
||||
query = query.IgnoreQueryFilters().Where(i => i.JobId == jobId);
|
||||
return await query.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Invoice?> GetByPaymentTokenAsync(string token)
|
||||
{
|
||||
return await _context.Set<Invoice>()
|
||||
.IgnoreQueryFilters()
|
||||
.Include(i => i.Customer)
|
||||
.Include(i => i.Job)
|
||||
.Include(i => i.InvoiceItems.Where(ii => !ii.IsDeleted))
|
||||
.FirstOrDefaultAsync(i => i.PaymentLinkToken == token && !i.IsDeleted);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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="PurchaseOrder"/> that provides domain-specific queries
|
||||
/// previously expressed inline in <c>PurchaseOrdersController</c>.
|
||||
/// </summary>
|
||||
public class PurchaseOrderRepository : Repository<PurchaseOrder>, IPurchaseOrderRepository
|
||||
{
|
||||
public PurchaseOrderRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<PurchaseOrder?> LoadForViewAsync(int id, int companyId)
|
||||
{
|
||||
return await _context.Set<PurchaseOrder>()
|
||||
.Where(p => p.Id == id && !p.IsDeleted && p.CompanyId == companyId)
|
||||
.Include(p => p.Vendor)
|
||||
.Include(p => p.Bill)
|
||||
.Include(p => p.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.InventoryItem)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<(List<PurchaseOrder> Items, int TotalCount)> GetPagedAsync(
|
||||
int companyId,
|
||||
int pageNumber,
|
||||
int pageSize,
|
||||
PurchaseOrderStatus? statusFilter = null,
|
||||
int? vendorId = null,
|
||||
DateTime? dateFrom = null,
|
||||
DateTime? dateTo = null,
|
||||
string? searchTerm = null,
|
||||
string? sortColumn = null,
|
||||
string? sortDirection = null)
|
||||
{
|
||||
var query = _context.Set<PurchaseOrder>()
|
||||
.Include(po => po.Vendor)
|
||||
.Include(po => po.Items.Where(i => !i.IsDeleted))
|
||||
.Where(po => !po.IsDeleted && po.CompanyId == 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();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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="Quote"/> that provides domain-specific queries previously
|
||||
/// scattered as inline EF expressions across <c>QuotesController</c> and
|
||||
/// <c>QuoteApprovalController</c>.
|
||||
/// </summary>
|
||||
public class QuoteRepository : Repository<Quote>, IQuoteRepository
|
||||
{
|
||||
public QuoteRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Quote?> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Quote?> 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<QuoteChangeHistory>> GetChangeHistoryAsync(int quoteId)
|
||||
{
|
||||
return await _context.QuoteChangeHistories
|
||||
.Where(h => h.QuoteId == quoteId && !h.IsDeleted)
|
||||
.Include(h => h.ChangedBy)
|
||||
.OrderByDescending(h => h.ChangedAt)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
using PowderCoating.Core.Interfaces.Repositories;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
|
||||
namespace PowderCoating.Infrastructure.Repositories;
|
||||
@@ -43,13 +44,13 @@ public class UnitOfWork : IUnitOfWork
|
||||
private IRepository<PowderUsageLog>? _powderUsageLogs;
|
||||
|
||||
// Core repositories
|
||||
private IRepository<Customer>? _customers;
|
||||
private IRepository<Job>? _jobs;
|
||||
private ICustomerRepository? _customers;
|
||||
private IJobRepository? _jobs;
|
||||
private IRepository<JobDailyPriority>? _jobDailyPriorities;
|
||||
private IRepository<JobItem>? _jobItems;
|
||||
private IRepository<JobItemCoat>? _jobItemCoats;
|
||||
private IRepository<JobChangeHistory>? _jobChangeHistories;
|
||||
private IRepository<Quote>? _quotes;
|
||||
private IQuoteRepository? _quotes;
|
||||
private IRepository<QuotePhoto>? _quotePhotos;
|
||||
private IRepository<QuoteItem>? _quoteItems;
|
||||
private IRepository<QuoteItemCoat>? _quoteItemCoats;
|
||||
@@ -109,7 +110,7 @@ public class UnitOfWork : IUnitOfWork
|
||||
private IRepository<GiftCertificateRedemption>? _giftCertificateRedemptions;
|
||||
|
||||
// Purchase Orders
|
||||
private IRepository<PurchaseOrder>? _purchaseOrders;
|
||||
private IPurchaseOrderRepository? _purchaseOrders;
|
||||
private IRepository<PurchaseOrderItem>? _purchaseOrderItems;
|
||||
|
||||
// Oven Scheduling
|
||||
@@ -117,14 +118,14 @@ public class UnitOfWork : IUnitOfWork
|
||||
private IRepository<OvenBatchItem>? _ovenBatchItems;
|
||||
|
||||
// Invoices, Payments & Deposits
|
||||
private IRepository<Invoice>? _invoices;
|
||||
private IInvoiceRepository? _invoices;
|
||||
private IRepository<InvoiceItem>? _invoiceItems;
|
||||
private IRepository<Payment>? _payments;
|
||||
private IRepository<Deposit>? _deposits;
|
||||
|
||||
// Expense Tracking / Accounts Payable
|
||||
private IRepository<Account>? _accounts;
|
||||
private IRepository<Bill>? _bills;
|
||||
private IBillRepository? _bills;
|
||||
private IRepository<BillLineItem>? _billLineItems;
|
||||
private IRepository<BillPayment>? _billPayments;
|
||||
private IRepository<Expense>? _expenses;
|
||||
@@ -171,12 +172,12 @@ public class UnitOfWork : IUnitOfWork
|
||||
|
||||
// Core repositories
|
||||
/// <summary>Repository for <see cref="Customer"/> records (commercial and non-commercial); tenant-filtered with soft delete.</summary>
|
||||
public IRepository<Customer> Customers =>
|
||||
_customers ??= new Repository<Customer>(_context);
|
||||
public ICustomerRepository Customers =>
|
||||
_customers ??= new CustomerRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="Job"/> records progressing through the 16-status lifecycle; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<Job> Jobs =>
|
||||
_jobs ??= new Repository<Job>(_context);
|
||||
public IJobRepository Jobs =>
|
||||
_jobs ??= new JobRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="JobDailyPriority"/> overrides that let supervisors re-order the shop floor queue.</summary>
|
||||
public IRepository<JobDailyPriority> JobDailyPriorities =>
|
||||
@@ -195,8 +196,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
_jobChangeHistories ??= new Repository<JobChangeHistory>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="Quote"/> records with multi-item pricing; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<Quote> Quotes =>
|
||||
_quotes ??= new Repository<Quote>(_context);
|
||||
public IQuoteRepository Quotes =>
|
||||
_quotes ??= new QuoteRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="QuotePhoto"/> AI photo uploads; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<QuotePhoto> QuotePhotos =>
|
||||
@@ -405,8 +406,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
|
||||
// Purchase Orders
|
||||
/// <summary>Repository for <see cref="PurchaseOrder"/> vendor purchase orders; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<PurchaseOrder> PurchaseOrders =>
|
||||
_purchaseOrders ??= new Repository<PurchaseOrder>(_context);
|
||||
public IPurchaseOrderRepository PurchaseOrders =>
|
||||
_purchaseOrders ??= new PurchaseOrderRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="PurchaseOrderItem"/> line-items on a purchase order; cascade-deleted with the PO.</summary>
|
||||
public IRepository<PurchaseOrderItem> PurchaseOrderItems =>
|
||||
@@ -423,8 +424,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
|
||||
// Invoices, Payments & Deposits
|
||||
/// <summary>Repository for <see cref="Invoice"/> customer invoices (1:1 with Job); tenant-filtered with soft delete.</summary>
|
||||
public IRepository<Invoice> Invoices =>
|
||||
_invoices ??= new Repository<Invoice>(_context);
|
||||
public IInvoiceRepository Invoices =>
|
||||
_invoices ??= new InvoiceRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="InvoiceItem"/> line-items on an invoice; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<InvoiceItem> InvoiceItems =>
|
||||
@@ -447,8 +448,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
_accounts ??= new Repository<Account>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="Bill"/> vendor bills (accounts payable); tenant-filtered with soft delete.</summary>
|
||||
public IRepository<Bill> Bills =>
|
||||
_bills ??= new Repository<Bill>(_context);
|
||||
public IBillRepository Bills =>
|
||||
_bills ??= new BillRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="BillLineItem"/> expense line-items on a vendor bill; each assigned to a chart-of-accounts entry.</summary>
|
||||
public IRepository<BillLineItem> BillLineItems =>
|
||||
|
||||
Reference in New Issue
Block a user