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:
@@ -1,4 +1,5 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Interfaces.Repositories;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces;
|
||||
|
||||
@@ -15,14 +16,14 @@ public interface IUnitOfWork : IDisposable
|
||||
// Powder Insights
|
||||
IRepository<PowderUsageLog> PowderUsageLogs { get; }
|
||||
|
||||
// Core entities
|
||||
IRepository<Customer> Customers { get; }
|
||||
IRepository<Job> Jobs { get; }
|
||||
// Core entities — typed repositories for complex domains
|
||||
ICustomerRepository Customers { get; }
|
||||
IJobRepository Jobs { get; }
|
||||
IRepository<JobDailyPriority> JobDailyPriorities { get; }
|
||||
IRepository<JobItem> JobItems { get; }
|
||||
IRepository<JobItemCoat> JobItemCoats { get; }
|
||||
IRepository<JobChangeHistory> JobChangeHistories { get; }
|
||||
IRepository<Quote> Quotes { get; }
|
||||
IQuoteRepository Quotes { get; }
|
||||
IRepository<QuotePhoto> QuotePhotos { get; }
|
||||
IRepository<QuoteItem> QuoteItems { get; }
|
||||
IRepository<QuoteItemCoat> QuoteItemCoats { get; }
|
||||
@@ -69,19 +70,19 @@ public interface IUnitOfWork : IDisposable
|
||||
IRepository<OvenBatch> OvenBatches { get; }
|
||||
IRepository<OvenBatchItem> OvenBatchItems { get; }
|
||||
|
||||
// Invoices, Payments & Deposits
|
||||
IRepository<Invoice> Invoices { get; }
|
||||
// Invoices, Payments & Deposits — typed repository for complex include chains
|
||||
IInvoiceRepository Invoices { get; }
|
||||
IRepository<InvoiceItem> InvoiceItems { get; }
|
||||
IRepository<Payment> Payments { get; }
|
||||
IRepository<Deposit> Deposits { get; }
|
||||
|
||||
// Purchase Orders
|
||||
IRepository<PurchaseOrder> PurchaseOrders { get; }
|
||||
// Purchase Orders — typed repository for paged/filtered list and detail load
|
||||
IPurchaseOrderRepository PurchaseOrders { get; }
|
||||
IRepository<PurchaseOrderItem> PurchaseOrderItems { get; }
|
||||
|
||||
// Expense Tracking / Accounts Payable
|
||||
// Expense Tracking / Accounts Payable — typed repository for Bills
|
||||
IRepository<Account> Accounts { get; }
|
||||
IRepository<Bill> Bills { get; }
|
||||
IBillRepository Bills { get; }
|
||||
IRepository<BillLineItem> BillLineItems { get; }
|
||||
IRepository<BillPayment> BillPayments { get; }
|
||||
IRepository<Expense> Expenses { get; }
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Typed repository for <see cref="Bill"/> that adds domain-specific queries on top of
|
||||
/// the generic CRUD interface.
|
||||
/// </summary>
|
||||
public interface IBillRepository : IRepository<Bill>
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a single bill with the full include chain required by the Details view: Vendor,
|
||||
/// APAccount, LineItems (filtered to non-deleted) with Account and Job navigations, and
|
||||
/// Payments (filtered to non-deleted) with BankAccount. Returns null if not found.
|
||||
/// </summary>
|
||||
Task<Bill?> LoadForViewAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single bill with only its line items for the Edit form. Excludes payment
|
||||
/// navigations since those are read-only after the bill is opened.
|
||||
/// </summary>
|
||||
Task<Bill?> LoadForEditAsync(int id);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Typed repository for <see cref="Customer"/> that adds domain-specific queries on top of
|
||||
/// the generic CRUD interface.
|
||||
/// </summary>
|
||||
public interface ICustomerRepository : IRepository<Customer>
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a single customer with the navigations needed by the Details view: PricingTier,
|
||||
/// and recent CustomerNotes ordered newest-first. Returns null if not found or soft-deleted.
|
||||
/// </summary>
|
||||
Task<Customer?> LoadForDetailsAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a customer by email address within the current tenant. Used for duplicate-email
|
||||
/// validation on create and edit. Returns null if no match is found.
|
||||
/// </summary>
|
||||
Task<Customer?> FindByEmailAsync(string email);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Typed repository for <see cref="Invoice"/> that adds domain-specific queries on top of
|
||||
/// the generic CRUD interface.
|
||||
/// </summary>
|
||||
public interface IInvoiceRepository : IRepository<Invoice>
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a single invoice with the full eight-table include chain required by the Details
|
||||
/// view and PDF generation: Customer, Job, PreparedBy, SalesTaxAccount, InvoiceItems with
|
||||
/// RevenueAccount and GeneratedGiftCertificate, Payments with RecordedBy and DepositAccount,
|
||||
/// Refunds with IssuedBy, CreditApplications with CreditMemo, and GiftCertificateRedemptions.
|
||||
/// Filtered includes exclude soft-deleted children. Returns null if not found or soft-deleted.
|
||||
/// </summary>
|
||||
Task<Invoice?> LoadForViewAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the invoice linked to a job, or null if none exists. Pass
|
||||
/// <paramref name="includeDeleted"/> = true to also surface soft-deleted invoices (used by
|
||||
/// the 1:1 uniqueness guard that prevents duplicate invoices for the same job).
|
||||
/// </summary>
|
||||
Task<Invoice?> GetForJobAsync(int jobId, bool includeDeleted = false);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up an invoice by its online-payment token. Ignores query filters so the payment
|
||||
/// portal can load the invoice even when the anonymous request has no tenant context.
|
||||
/// Returns null if the token does not match any invoice.
|
||||
/// </summary>
|
||||
Task<Invoice?> GetByPaymentTokenAsync(string token);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Typed repository for <see cref="Job"/> that extends the generic CRUD interface with
|
||||
/// domain-specific queries that require multi-level include chains the generic
|
||||
/// <see cref="IRepository{T}"/> cannot express.
|
||||
/// </summary>
|
||||
public interface IJobRepository : IRepository<Job>
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads all active jobs with the minimal set of navigations needed to render the Kanban
|
||||
/// board columns (Customer name, status, priority, assigned user, due date). Uses
|
||||
/// AsNoTracking for read performance.
|
||||
/// </summary>
|
||||
Task<List<Job>> GetBoardJobsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single job with the full include chain required by the Details view: Customer,
|
||||
/// JobStatus, JobPriority, AssignedUser, Quote, OvenCost, OriginalJob, IntakeCheckedBy,
|
||||
/// and all JobItems with their Coats (InventoryItem + Vendor) and PrepServices.
|
||||
/// Also loads JobPrepServices (job-level prep) separately. Returns null if not found.
|
||||
/// </summary>
|
||||
Task<Job?> LoadForDetailsAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single job with the include chain required by the Edit form: same as
|
||||
/// <see cref="LoadForDetailsAsync"/> but without the read-only audit navigations, and
|
||||
/// with tracking enabled so changes can be saved.
|
||||
/// </summary>
|
||||
Task<Job?> LoadForEditAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the lightweight job record needed for status-change operations (MoveCard, StatusBump).
|
||||
/// Includes only JobStatus. Returns null if not found or soft-deleted.
|
||||
/// </summary>
|
||||
Task<Job?> LoadForStatusChangeAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the change history for a job, ordered newest-first, with ChangedBy navigation
|
||||
/// loaded. Used by the Details view changelog tab.
|
||||
/// </summary>
|
||||
Task<List<JobChangeHistory>> GetChangeHistoryAsync(int jobId);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Enums;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Typed repository for <see cref="PurchaseOrder"/> that adds domain-specific queries on top of
|
||||
/// the generic CRUD interface.
|
||||
/// </summary>
|
||||
public interface IPurchaseOrderRepository : IRepository<PurchaseOrder>
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a single purchase order with the full include chain required by the Details view:
|
||||
/// Vendor, Bill, and Items (filtered to non-deleted) with InventoryItem navigation.
|
||||
/// Returns null if not found or not owned by the current tenant.
|
||||
/// </summary>
|
||||
Task<PurchaseOrder?> LoadForViewAsync(int id, int companyId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a paged, filtered, and sorted list of purchase orders for the Index view.
|
||||
/// All filter parameters are optional — passing null/empty applies no restriction for
|
||||
/// that dimension.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using PowderCoating.Core.Entities;
|
||||
|
||||
namespace PowderCoating.Core.Interfaces.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Typed repository for <see cref="Quote"/> that adds domain-specific queries on top of
|
||||
/// the generic CRUD interface.
|
||||
/// </summary>
|
||||
public interface IQuoteRepository : IRepository<Quote>
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a single quote with the full include chain required by the Details view: Customer,
|
||||
/// PreparedBy, QuoteStatus, OvenCost, QuoteItems with Coats (InventoryItem + Vendor),
|
||||
/// CatalogItem, and PrepServices; plus QuotePrepServices (quote-level prep).
|
||||
/// Returns null if not found or soft-deleted.
|
||||
/// </summary>
|
||||
Task<Quote?> LoadForDetailsAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single quote by its customer-facing approval token. Ignores global query filters
|
||||
/// so the unauthenticated approval portal can resolve any tenant's quote by token alone.
|
||||
/// Includes Customer navigation. Returns null if the token does not match any live quote.
|
||||
/// </summary>
|
||||
Task<Quote?> GetByApprovalTokenAsync(string token);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the change history for a quote, ordered newest-first, with ChangedBy loaded.
|
||||
/// </summary>
|
||||
Task<List<QuoteChangeHistory>> GetChangeHistoryAsync(int quoteId);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Moved to PowderCoating.Application.Interfaces.IFinancialReportService — Application layer owns DTO-returning service interfaces.
|
||||
namespace PowderCoating.Core.Interfaces.Services;
|
||||
@@ -0,0 +1,2 @@
|
||||
// Moved to PowderCoating.Application.Interfaces.IOperationalReportService — Application layer owns DTO-returning service interfaces.
|
||||
namespace PowderCoating.Core.Interfaces.Services;
|
||||
Reference in New Issue
Block a user