using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;
using PowderCoating.Infrastructure.Data;
namespace PowderCoating.Infrastructure.Repositories;
///
/// Concrete implementation of that coordinates all entity repositories
/// and exposes transaction management for the application layer.
///
/// All entity repositories are lazily instantiated — a is only
/// allocated the first time the corresponding property is accessed. This keeps the per-request
/// memory footprint small when only a handful of entities are needed.
///
///
/// Persistence: call or
/// (identical behaviour) to flush pending changes to the database.
///
///
/// Explicit transactions: use /
/// / when multiple
/// SaveChanges calls must succeed or fail atomically. For a simpler fire-and-forget pattern,
/// prefer which wraps the operation in an
/// EF Core execution-strategy-aware transaction automatically.
///
///
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _context;
private IDbContextTransaction? _transaction;
// Multi-tenancy
private IRepository? _companies;
private IRepository? _companyOperatingCosts;
private IRepository? _companyPreferences;
// AI Predictions
private IRepository? _aiItemPredictions;
// Powder Insights
private IRepository? _powderUsageLogs;
// Core repositories
private IRepository? _customers;
private IRepository? _jobs;
private IRepository? _jobDailyPriorities;
private IRepository? _jobItems;
private IRepository? _jobItemCoats;
private IRepository? _jobChangeHistories;
private IRepository? _quotes;
private IRepository? _quotePhotos;
private IRepository? _quoteItems;
private IRepository? _quoteItemCoats;
private IRepository? _quoteItemPrepServices;
private IRepository? _quoteChangeHistories;
private IRepository? _inventoryItems;
private IRepository? _inventoryTransactions;
private IRepository? _equipment;
private IRepository? _ovenCosts;
private IRepository? _blastSetups;
private IRepository? _maintenanceRecords;
private IRepository? _vendors;
private IRepository? _jobPhotos;
private IRepository? _jobNotes;
private IRepository? _customerNotes;
private IRepository? _jobStatusHistory;
private IRepository? _pricingTiers;
// Lookup tables (replacing enums)
private IRepository? _jobStatusLookups;
private IRepository? _jobPriorityLookups;
private IRepository? _quoteStatusLookups;
private IRepository? _inventoryCategoryLookups;
private IRepository? _appointmentStatusLookups;
private IRepository? _appointmentTypeLookups;
private IRepository? _prepServices;
private IRepository? _shopWorkers;
// Appointments
private IRepository? _appointments;
// Product Catalog
private IRepository? _catalogCategories;
private IRepository? _catalogItems;
private IRepository? _catalogPriceCheckReports;
// Notifications
private IRepository? _notificationLogs;
private IRepository? _notificationTemplates;
// Subscription
private IRepository? _subscriptionPlanConfigs;
// Job Templates
private IRepository? _jobTemplates;
private IRepository? _jobTemplateItems;
private IRepository? _jobTemplateItemCoats;
private IRepository? _jobTemplateItemPrepServices;
// Bug Reports
private IRepository? _bugReports;
private IRepository? _contactSubmissions;
private IRepository? _manufacturerLookupPatterns;
// Gift Certificates
private IRepository? _giftCertificates;
private IRepository? _giftCertificateRedemptions;
// Purchase Orders
private IRepository? _purchaseOrders;
private IRepository? _purchaseOrderItems;
// Oven Scheduling
private IRepository? _ovenBatches;
private IRepository? _ovenBatchItems;
// Invoices, Payments & Deposits
private IRepository? _invoices;
private IRepository? _invoiceItems;
private IRepository? _payments;
private IRepository? _deposits;
// Expense Tracking / Accounts Payable
private IRepository? _accounts;
private IRepository? _bills;
private IRepository? _billLineItems;
private IRepository? _billPayments;
private IRepository? _expenses;
///
/// Initialises the unit of work with the scoped .
/// The context is shared across all repositories created by this instance so that
/// all reads and writes within a single request participate in the same EF Core identity map
/// and change-tracker, enabling zero-copy entity sharing between repositories.
///
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
}
// -------------------------------------------------------------------------
// Repository properties — lazy-initialised via the null-coalescing assignment
// operator (??=). Each property creates one Repository instance at most
// per UnitOfWork lifetime (= per HTTP request when registered as Scoped).
// Repositories share the underlying DbContext so they share the change tracker.
// -------------------------------------------------------------------------
/// Repository for tenant records. Soft-delete filter only (no tenant filter — SuperAdmin manages all companies).
public IRepository Companies =>
_companies ??= new Repository(_context);
/// Repository for (one-to-one with Company).
public IRepository CompanyOperatingCosts =>
_companyOperatingCosts ??= new Repository(_context);
/// Repository for (one-to-one with Company).
public IRepository CompanyPreferences =>
_companyPreferences ??= new Repository(_context);
// AI Predictions
/// Repository for records; tenant-filtered. Shared between QuoteItem and JobItem via a single nullable FK — no duplication on quote→job conversion.
public IRepository AiItemPredictions =>
_aiItemPredictions ??= new Repository(_context);
// Powder Insights
/// Repository for records capturing per-coat powder consumption; used by powder-usage analytics.
public IRepository PowderUsageLogs =>
_powderUsageLogs ??= new Repository(_context);
// Core repositories
/// Repository for records (commercial and non-commercial); tenant-filtered with soft delete.
public IRepository Customers =>
_customers ??= new Repository(_context);
/// Repository for records progressing through the 16-status lifecycle; tenant-filtered with soft delete.
public IRepository Jobs =>
_jobs ??= new Repository(_context);
/// Repository for overrides that let supervisors re-order the shop floor queue.
public IRepository JobDailyPriorities =>
_jobDailyPriorities ??= new Repository(_context);
/// Repository for line-items; tenant-filtered with soft delete.
public IRepository JobItems =>
_jobItems ??= new Repository(_context);
/// Repository for powder coat passes; tenant-filtered with soft delete.
public IRepository JobItemCoats =>
_jobItemCoats ??= new Repository(_context);
/// Repository for audit entries; tenant-filtered with soft delete.
public IRepository JobChangeHistories =>
_jobChangeHistories ??= new Repository(_context);
/// Repository for records with multi-item pricing; tenant-filtered with soft delete.
public IRepository Quotes =>
_quotes ??= new Repository(_context);
/// Repository for AI photo uploads; tenant-filtered with soft delete.
public IRepository QuotePhotos =>
_quotePhotos ??= new Repository(_context);
/// Repository for line-items; tenant-filtered with soft delete.
public IRepository QuoteItems =>
_quoteItems ??= new Repository(_context);
/// Repository for powder coat passes on a quote item; tenant-filtered with soft delete.
public IRepository QuoteItemCoats =>
_quoteItemCoats ??= new Repository(_context);
/// Repository for prep-service assignments on a quote item; tenant-filtered with soft delete.
public IRepository QuoteItemPrepServices =>
_quoteItemPrepServices ??= new Repository(_context);
/// Repository for audit entries; tenant-filtered with soft delete.
public IRepository QuoteChangeHistories =>
_quoteChangeHistories ??= new Repository(_context);
/// Repository for powder and material stock; tenant-filtered with soft delete.
public IRepository InventoryItems =>
_inventoryItems ??= new Repository(_context);
/// Repository for stock movements; tenant-filtered with soft delete.
public IRepository InventoryTransactions =>
_inventoryTransactions ??= new Repository(_context);
/// Repository for records (ovens, sandblasters, booths); tenant-filtered with soft delete.
public IRepository Equipment =>
_equipment ??= new Repository(_context);
/// Repository for named oven configurations used by the Oven Scheduler; tenant-filtered with soft delete.
public IRepository OvenCosts =>
_ovenCosts ??= new Repository(_context);
/// Repository for named blast setups; tenant-filtered with soft delete.
public IRepository BlastSetups =>
_blastSetups ??= new Repository(_context);
/// Repository for equipment maintenance records; tenant-filtered with soft delete.
public IRepository MaintenanceRecords =>
_maintenanceRecords ??= new Repository(_context);
/// Repository for supplier records; tenant-filtered with soft delete.
public IRepository Vendors =>
_vendors ??= new Repository(_context);
/// Repository for attachments; tenant-filtered with soft delete.
public IRepository JobPhotos =>
_jobPhotos ??= new Repository(_context);
/// Repository for free-text staff notes on jobs; tenant-filtered with soft delete.
public IRepository JobNotes =>
_jobNotes ??= new Repository(_context);
/// Repository for free-text staff notes on customer records; tenant-filtered with soft delete.
public IRepository CustomerNotes =>
_customerNotes ??= new Repository(_context);
/// Repository for status-transition audit records; tenant-filtered with soft delete.
public IRepository JobStatusHistory =>
_jobStatusHistory ??= new Repository(_context);
/// Repository for customer discount tiers; tenant-filtered with soft delete.
public IRepository PricingTiers =>
_pricingTiers ??= new Repository(_context);
// Lookup tables (replacing enums)
// These are stored in the database instead of C# enums so that tenants can
// customise display names, colours, and display order without a code deployment.
/// Repository for DB-backed job status definitions.
public IRepository JobStatusLookups =>
_jobStatusLookups ??= new Repository(_context);
/// Repository for DB-backed job priority definitions.
public IRepository JobPriorityLookups =>
_jobPriorityLookups ??= new Repository(_context);
/// Repository for DB-backed quote status definitions.
public IRepository QuoteStatusLookups =>
_quoteStatusLookups ??= new Repository(_context);
/// Repository for user-defined inventory categories.
public IRepository InventoryCategoryLookups =>
_inventoryCategoryLookups ??= new Repository(_context);
/// Repository for appointment status definitions.
public IRepository AppointmentStatusLookups =>
_appointmentStatusLookups ??= new Repository(_context);
/// Repository for appointment type definitions.
public IRepository AppointmentTypeLookups =>
_appointmentTypeLookups ??= new Repository(_context);
/// Repository for prep-service catalog entries shared across quotes and jobs.
public IRepository PrepServices =>
_prepServices ??= new Repository(_context);
/// Repository for profiles with role assignments; tenant-filtered with soft delete.
public IRepository ShopWorkers =>
_shopWorkers ??= new Repository(_context);
/// Repository for per-role labour cost rates; unique on (CompanyId, Role).
private IRepository? _shopWorkerRoleCosts;
public IRepository ShopWorkerRoleCosts =>
_shopWorkerRoleCosts ??= new Repository(_context);
/// Repository for quality-failure and remediation records; tenant-filtered with soft delete.
private IRepository? _reworkRecords;
public IRepository ReworkRecords =>
_reworkRecords ??= new Repository(_context);
/// Repository for customer refund records; tenant-filtered with soft delete.
private IRepository? _refunds;
public IRepository Refunds =>
_refunds ??= new Repository(_context);
/// Repository for records issued to customers; unique memo number per company.
private IRepository? _creditMemos;
public IRepository CreditMemos =>
_creditMemos ??= new Repository(_context);
/// Repository for records linking credit memos to specific invoices.
private IRepository? _creditMemoApplications;
public IRepository CreditMemoApplications =>
_creditMemoApplications ??= new Repository(_context);
/// Repository for clock-in/clock-out worker time records; used for labour-cost calculations.
private IRepository? _jobTimeEntries;
public IRepository JobTimeEntries =>
_jobTimeEntries ??= new Repository(_context);
// Appointments
/// Repository for customer appointment records; tenant-filtered with soft delete.
public IRepository Appointments =>
_appointments ??= new Repository(_context);
// Product Catalog
/// Repository for hierarchical service catalog categories; tenant-filtered with soft delete.
public IRepository CatalogCategories =>
_catalogCategories ??= new Repository(_context);
/// Repository for pre-priced service catalog items; tenant-filtered with soft delete.
public IRepository CatalogItems =>
_catalogItems ??= new Repository(_context);
/// Repository for AI price-check results archived per company.
public IRepository CatalogPriceCheckReports =>
_catalogPriceCheckReports ??= new Repository(_context);
// Notifications
/// Repository for outbound notification audit records; tenant-filtered with soft delete.
public IRepository NotificationLogs =>
_notificationLogs ??= new Repository(_context);
/// Repository for per-company channel template overrides; unique on (CompanyId, Type, Channel).
public IRepository NotificationTemplates =>
_notificationTemplates ??= new Repository(_context);
// Subscription
/// Repository for Stripe plan definitions; global (no tenant filter).
public IRepository SubscriptionPlanConfigs =>
_subscriptionPlanConfigs ??= new Repository(_context);
// Bug Reports
/// Repository for user-submitted bug reports; tenant-filtered with soft delete.
public IRepository BugReports =>
_bugReports ??= new Repository(_context);
// Contact Us
/// Repository for contact form submissions; platform admins see all, company users see their own.
public IRepository ContactSubmissions =>
_contactSubmissions ??= new Repository(_context);
/// Repository for global AI URL patterns; soft-delete only (CompanyId = 0 by convention).
public IRepository ManufacturerLookupPatterns =>
_manufacturerLookupPatterns ??= new Repository(_context);
// Gift Certificates
/// Repository for records; certificate code unique per company.
public IRepository GiftCertificates =>
_giftCertificates ??= new Repository(_context);
/// Repository for usage events against a gift certificate.
public IRepository GiftCertificateRedemptions =>
_giftCertificateRedemptions ??= new Repository(_context);
// Job Templates
/// Repository for reusable job blueprints; tenant-filtered with soft delete.
public IRepository JobTemplates =>
_jobTemplates ??= new Repository(_context);
/// Repository for item definitions within a job template.
public IRepository JobTemplateItems =>
_jobTemplateItems ??= new Repository(_context);
/// Repository for coat definitions within a job template item.
public IRepository JobTemplateItemCoats =>
_jobTemplateItemCoats ??= new Repository(_context);
/// Repository for prep-service definitions within a job template item.
public IRepository JobTemplateItemPrepServices =>
_jobTemplateItemPrepServices ??= new Repository(_context);
// Purchase Orders
/// Repository for vendor purchase orders; tenant-filtered with soft delete.
public IRepository PurchaseOrders =>
_purchaseOrders ??= new Repository(_context);
/// Repository for line-items on a purchase order; cascade-deleted with the PO.
public IRepository PurchaseOrderItems =>
_purchaseOrderItems ??= new Repository(_context);
// Oven Scheduling
/// Repository for scheduled oven cure batches; tenant-filtered with soft delete.
public IRepository OvenBatches =>
_ovenBatches ??= new Repository(_context);
/// Repository for job/item assignments within an oven batch.
public IRepository OvenBatchItems =>
_ovenBatchItems ??= new Repository(_context);
// Invoices, Payments & Deposits
/// Repository for customer invoices (1:1 with Job); tenant-filtered with soft delete.
public IRepository Invoices =>
_invoices ??= new Repository(_context);
/// Repository for line-items on an invoice; tenant-filtered with soft delete.
public IRepository InvoiceItems =>
_invoiceItems ??= new Repository(_context);
/// Repository for customer payment records against invoices; tenant-filtered with soft delete.
public IRepository Payments =>
_payments ??= new Repository(_context);
///
/// Repository for pre-invoice deposit records.
/// Unapplied deposits are auto-swept into records when an invoice is created.
///
public IRepository Deposits =>
_deposits ??= new Repository(_context);
// Expense Tracking / Accounts Payable
/// Repository for chart-of-accounts entries; supports self-referencing parent/child hierarchy.
public IRepository Accounts =>
_accounts ??= new Repository(_context);
/// Repository for vendor bills (accounts payable); tenant-filtered with soft delete.
public IRepository Bills =>
_bills ??= new Repository(_context);
/// Repository for expense line-items on a vendor bill; each assigned to a chart-of-accounts entry.
public IRepository BillLineItems =>
_billLineItems ??= new Repository(_context);
/// Repository for cash disbursement records against a vendor bill.
public IRepository BillPayments =>
_billPayments ??= new Repository(_context);
/// Repository for ad-hoc non-bill expense records; tenant-filtered with soft delete.
public IRepository Expenses =>
_expenses ??= new Repository(_context);
///
/// Flushes all pending changes in the EF Core change tracker to the database.
/// Returns the number of state entries written.
///
/// Internally delegates to which
/// automatically stamps CreatedAt, UpdatedAt, CompanyId, and audit
/// fields on every modified entity before the SQL is sent.
///
///
public async Task SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
///
/// Alias for ; provided so controllers can use the more
/// expressive await _unitOfWork.CompleteAsync() idiom that signals intent
/// ("I am done with this unit of work") rather than the more technical "save changes".
/// Both methods are functionally identical.
///
public async Task CompleteAsync()
{
return await SaveChangesAsync();
}
///
/// Executes inside a database transaction that is automatically
/// committed if the operation succeeds or rolled back if it throws.
///
/// Uses the EF Core IExecutionStrategy so that the transaction is automatically
/// retried on transient SQL errors (e.g., deadlocks) when the database provider supports it.
/// This is the preferred approach over the manual /
/// pair for most use cases.
///
///
/// An async delegate whose work must be atomic. Do NOT call SaveChangesAsync inside — it is called automatically before commit.
public async Task ExecuteInTransactionAsync(Func operation)
{
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync