Phases 3 & 4: Complete data access architecture migration
Phase 3 — eliminated ApplicationDbContext from all non-exempt controllers, routing all data access through IUnitOfWork. Added IPlainRepository<T> for the four platform entities (Announcement, BannedIp, DashboardTip, ReleaseNote) that intentionally don't extend BaseEntity and therefore can't use the constrained IRepository<T>. Added permanent-exception comments to the 18 controllers that legitimately retain direct DbContext access (Identity infra, cross-tenant platform ops, bulk streaming exports). Phase 4 — added EnforceDataAccessArchitecture() to Program.cs, a startup gate that reflects over every Controller subclass and throws at boot if any non-exempt controller injects ApplicationDbContext. The app cannot start with a violation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -89,4 +89,17 @@ public class BillRepository : Repository<Bill>, IBillRepository
|
||||
.Select(p => p.PaymentNumber)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<Bill>> GetForDateRangeAsync(DateTime start, DateTime end)
|
||||
{
|
||||
return await _context.Bills
|
||||
.Where(b => !b.IsDeleted && b.BillDate >= start && b.BillDate <= end)
|
||||
.Include(b => b.Vendor)
|
||||
.Include(b => b.LineItems.Where(li => !li.IsDeleted))
|
||||
.ThenInclude(li => li.Account)
|
||||
.Include(b => b.Payments.Where(p => !p.IsDeleted))
|
||||
.OrderBy(b => b.BillDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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="InventoryTransaction"/> that adds a dynamic-filter
|
||||
/// ledger query on top of the generic <see cref="Repository{T}"/>.
|
||||
/// </summary>
|
||||
public class InventoryTransactionRepository : Repository<InventoryTransaction>, IInventoryTransactionRepository
|
||||
{
|
||||
public InventoryTransactionRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<InventoryTransaction>> GetForLedgerAsync(
|
||||
int? itemId,
|
||||
DateTime? from,
|
||||
DateTime? to,
|
||||
InventoryTransactionType? type)
|
||||
{
|
||||
var query = _context.InventoryTransactions
|
||||
.AsNoTracking()
|
||||
.Include(t => t.InventoryItem)
|
||||
.Include(t => t.PurchaseOrder)
|
||||
.Include(t => t.Job)
|
||||
.Where(t => !t.IsDeleted);
|
||||
|
||||
if (itemId.HasValue)
|
||||
query = query.Where(t => t.InventoryItemId == itemId.Value);
|
||||
if (from.HasValue)
|
||||
query = query.Where(t => t.TransactionDate >= from.Value);
|
||||
if (to.HasValue)
|
||||
query = query.Where(t => t.TransactionDate < to.Value.AddDays(1));
|
||||
if (type.HasValue)
|
||||
query = query.Where(t => t.TransactionType == type.Value);
|
||||
|
||||
return await query
|
||||
.OrderByDescending(t => t.TransactionDate)
|
||||
.Take(500)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
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="JobItemCoat"/> that adds ThenInclude-based load methods the
|
||||
/// generic <see cref="Repository{T}"/> cannot express.
|
||||
/// </summary>
|
||||
public class JobItemCoatRepository : Repository<JobItemCoat>, IJobItemCoatRepository
|
||||
{
|
||||
public JobItemCoatRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<JobItemCoat?> LoadForOrderMarkingAsync(int id)
|
||||
{
|
||||
return await _context.JobItemCoats
|
||||
.Include(c => c.JobItem).ThenInclude(i => i.Job).ThenInclude(j => j.Customer)
|
||||
.Include(c => c.Vendor)
|
||||
.Include(c => c.InventoryItem).ThenInclude(i => i!.PrimaryVendor)
|
||||
.FirstOrDefaultAsync(c => c.Id == id);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<JobItemCoat?> LoadWithInventoryAsync(int id)
|
||||
{
|
||||
return await _context.JobItemCoats
|
||||
.Include(c => c.InventoryItem)
|
||||
.FirstOrDefaultAsync(c => c.Id == id);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<JobItemCoat?> LoadWithJobChainAsync(int id)
|
||||
{
|
||||
return await _context.JobItemCoats
|
||||
.Include(c => c.JobItem).ThenInclude(i => i.Job)
|
||||
.FirstOrDefaultAsync(c => c.Id == id);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<JobItemCoat>> GetCandidateCoatsForLinkingAsync(int excludeCoatId, int companyId)
|
||||
{
|
||||
return await _context.JobItemCoats
|
||||
.Include(c => c.JobItem)
|
||||
.Where(c => c.Id != excludeCoatId
|
||||
&& c.InventoryItemId == null
|
||||
&& c.JobItem.CompanyId == companyId)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
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="JobPhoto"/> that provides inventory-specific photo
|
||||
/// lookup queries requiring multi-level ThenInclude chains that the generic
|
||||
/// <see cref="Repository{T}"/> cannot express.
|
||||
/// </summary>
|
||||
public class JobPhotoRepository : Repository<JobPhoto>, IJobPhotoRepository
|
||||
{
|
||||
public JobPhotoRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<JobPhoto>> GetTaggedPhotosAsync(string? colorName, string? itemName)
|
||||
{
|
||||
var query = _context.JobPhotos
|
||||
.AsNoTracking()
|
||||
.Include(p => p.Job)
|
||||
.ThenInclude(j => j!.Customer)
|
||||
.Where(p => !p.IsDeleted && p.Tags != null && p.Tags != "");
|
||||
|
||||
if (!string.IsNullOrEmpty(colorName) && !string.IsNullOrEmpty(itemName) && colorName != itemName)
|
||||
query = query.Where(p => p.Tags!.ToLower().Contains(colorName) || p.Tags!.ToLower().Contains(itemName));
|
||||
else if (!string.IsNullOrEmpty(colorName))
|
||||
query = query.Where(p => p.Tags!.ToLower().Contains(colorName));
|
||||
else if (!string.IsNullOrEmpty(itemName))
|
||||
query = query.Where(p => p.Tags!.ToLower().Contains(itemName));
|
||||
|
||||
return await query.OrderByDescending(p => p.UploadedDate).ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<JobPhoto>> GetPhotosByPowderItemAsync(int inventoryItemId)
|
||||
{
|
||||
return await _context.JobPhotos
|
||||
.AsNoTracking()
|
||||
.Include(p => p.Job)
|
||||
.ThenInclude(j => j!.Customer)
|
||||
.Include(p => p.Job)
|
||||
.ThenInclude(j => j!.JobItems)
|
||||
.ThenInclude(ji => ji.Coats)
|
||||
.Where(p => !p.IsDeleted &&
|
||||
p.Job != null &&
|
||||
p.Job.JobItems.Any(ji => ji.Coats.Any(c => c.InventoryItemId == inventoryItemId)))
|
||||
.OrderByDescending(p => p.UploadedDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -175,4 +175,16 @@ public class JobRepository : Repository<Job>, IJobRepository
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Job?> LoadForTemplateSnapshotAsync(int jobId)
|
||||
{
|
||||
return await _context.Jobs
|
||||
.Where(j => j.Id == jobId && !j.IsDeleted)
|
||||
.Include(j => j.JobItems.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.Coats.Where(c => !c.IsDeleted))
|
||||
.Include(j => j.JobItems.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.PrepServices.Where(p => !p.IsDeleted))
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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="JobTemplate"/> 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 the read queries
|
||||
/// that require ThenInclude chains for items, coats, and prep services.
|
||||
/// </summary>
|
||||
public class JobTemplateRepository : Repository<JobTemplate>, IJobTemplateRepository
|
||||
{
|
||||
public JobTemplateRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<JobTemplate?> LoadForDetailsAsync(int id)
|
||||
{
|
||||
return await _context.JobTemplates
|
||||
.Where(t => t.Id == id && !t.IsDeleted)
|
||||
.Include(t => t.Customer)
|
||||
.Include(t => t.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.Coats.Where(c => !c.IsDeleted))
|
||||
.ThenInclude(c => c.InventoryItem)
|
||||
.Include(t => t.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.PrepServices.Where(p => !p.IsDeleted))
|
||||
.ThenInclude(p => p.PrepService)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<JobTemplate>> GetAllActiveWithFullIncludesAsync()
|
||||
{
|
||||
return await _context.JobTemplates
|
||||
.Where(t => !t.IsDeleted && t.IsActive)
|
||||
.Include(t => t.Customer)
|
||||
.Include(t => t.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.Coats.Where(c => !c.IsDeleted))
|
||||
.Include(t => t.Items.Where(i => !i.IsDeleted))
|
||||
.ThenInclude(i => i.PrepServices.Where(p => !p.IsDeleted))
|
||||
.ThenInclude(p => p.PrepService)
|
||||
.OrderBy(t => t.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Enums;
|
||||
using PowderCoating.Core.Interfaces.Repositories;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
|
||||
@@ -60,4 +61,69 @@ public class NotificationLogRepository : Repository<NotificationLog>, INotificat
|
||||
.Where(n => n.JobId == jobId)
|
||||
.OrderByDescending(n => n.SentAt)
|
||||
.ToListAsync();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<(List<NotificationLog> Items, int TotalCount)> GetPagedFilteredAsync(
|
||||
int pageNumber, int pageSize,
|
||||
string? searchTerm = null,
|
||||
NotificationChannel? channel = null,
|
||||
NotificationStatus? status = null,
|
||||
NotificationType? type = null,
|
||||
int? jobId = null,
|
||||
string sortColumn = "SentAt",
|
||||
string sortDirection = "desc")
|
||||
{
|
||||
var query = _dbSet
|
||||
.AsNoTracking()
|
||||
.Include(n => n.Customer)
|
||||
.Include(n => n.Job)
|
||||
.Include(n => n.Quote)
|
||||
.AsQueryable();
|
||||
|
||||
if (jobId.HasValue)
|
||||
query = query.Where(n => n.JobId == jobId.Value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
var s = searchTerm.ToLower();
|
||||
query = query.Where(n =>
|
||||
n.RecipientName.ToLower().Contains(s) ||
|
||||
n.Recipient.ToLower().Contains(s) ||
|
||||
(n.Subject != null && n.Subject.ToLower().Contains(s)) ||
|
||||
(n.Job != null && n.Job.JobNumber.ToLower().Contains(s)) ||
|
||||
(n.Quote != null && n.Quote.QuoteNumber.ToLower().Contains(s)));
|
||||
}
|
||||
|
||||
if (channel.HasValue)
|
||||
query = query.Where(n => n.Channel == channel.Value);
|
||||
|
||||
if (status.HasValue)
|
||||
query = query.Where(n => n.Status == status.Value);
|
||||
|
||||
if (type.HasValue)
|
||||
query = query.Where(n => n.NotificationType == type.Value);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
query = (sortColumn, sortDirection) switch
|
||||
{
|
||||
("RecipientName", "asc") => query.OrderBy(n => n.RecipientName),
|
||||
("RecipientName", _) => query.OrderByDescending(n => n.RecipientName),
|
||||
("Channel", "asc") => query.OrderBy(n => n.Channel),
|
||||
("Channel", _) => query.OrderByDescending(n => n.Channel),
|
||||
("Status", "asc") => query.OrderBy(n => n.Status),
|
||||
("Status", _) => query.OrderByDescending(n => n.Status),
|
||||
("Type", "asc") => query.OrderBy(n => n.NotificationType),
|
||||
("Type", _) => query.OrderByDescending(n => n.NotificationType),
|
||||
(_, "asc") => query.OrderBy(n => n.SentAt),
|
||||
_ => query.OrderByDescending(n => n.SentAt)
|
||||
};
|
||||
|
||||
var items = await query
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
|
||||
namespace PowderCoating.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Generic repository for platform-level entities that do not inherit BaseEntity
|
||||
/// (Announcement, BannedIp, DashboardTip, ReleaseNote). No global query filters apply
|
||||
/// to these entities, so no IgnoreQueryFilters support is needed. All writes are staged
|
||||
/// in the EF change tracker — call IUnitOfWork.CompleteAsync() to flush.
|
||||
/// </summary>
|
||||
public class PlainRepository<T> : IPlainRepository<T> where T : class
|
||||
{
|
||||
protected readonly ApplicationDbContext _context;
|
||||
protected readonly DbSet<T> _dbSet;
|
||||
|
||||
public PlainRepository(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
_dbSet = context.Set<T>();
|
||||
}
|
||||
|
||||
public virtual async Task<T?> GetByIdAsync(int id)
|
||||
=> await _dbSet.FindAsync(id);
|
||||
|
||||
public virtual async Task<IEnumerable<T>> GetAllAsync()
|
||||
=> await _dbSet.ToListAsync();
|
||||
|
||||
public virtual async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate)
|
||||
=> await _dbSet.Where(predicate).ToListAsync();
|
||||
|
||||
public virtual async Task<T?> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate)
|
||||
=> await _dbSet.FirstOrDefaultAsync(predicate);
|
||||
|
||||
public virtual async Task<bool> AnyAsync(Expression<Func<T, bool>> predicate)
|
||||
=> await _dbSet.AnyAsync(predicate);
|
||||
|
||||
public virtual async Task<int> CountAsync(Expression<Func<T, bool>>? predicate = null)
|
||||
=> predicate == null ? await _dbSet.CountAsync() : await _dbSet.CountAsync(predicate);
|
||||
|
||||
public virtual async Task<T> AddAsync(T entity)
|
||||
{
|
||||
await _dbSet.AddAsync(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities)
|
||||
{
|
||||
await _dbSet.AddRangeAsync(entities);
|
||||
return entities;
|
||||
}
|
||||
|
||||
public virtual Task UpdateAsync(T entity)
|
||||
{
|
||||
_dbSet.Update(entity);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(T entity)
|
||||
{
|
||||
_dbSet.Remove(entity);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
await DeleteAsync(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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="PowderUsageLog"/> that adds a dynamic-filter
|
||||
/// ledger query on top of the generic <see cref="Repository{T}"/>.
|
||||
/// </summary>
|
||||
public class PowderUsageLogRepository : Repository<PowderUsageLog>, IPowderUsageLogRepository
|
||||
{
|
||||
public PowderUsageLogRepository(ApplicationDbContext context) : base(context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<PowderUsageLog>> GetForLedgerAsync(int? itemId, DateTime? from, DateTime? to)
|
||||
{
|
||||
var query = _context.PowderUsageLogs
|
||||
.AsNoTracking()
|
||||
.Include(u => u.Job)
|
||||
.ThenInclude(j => j!.Customer)
|
||||
.Include(u => u.InventoryItem)
|
||||
.Include(u => u.JobItemCoat)
|
||||
.Where(u => !u.IsDeleted);
|
||||
|
||||
if (itemId.HasValue)
|
||||
query = query.Where(u => u.InventoryItemId == itemId.Value);
|
||||
if (from.HasValue)
|
||||
query = query.Where(u => u.RecordedAt >= from.Value);
|
||||
if (to.HasValue)
|
||||
query = query.Where(u => u.RecordedAt < to.Value.AddDays(1));
|
||||
|
||||
return await query
|
||||
.OrderByDescending(u => u.RecordedAt)
|
||||
.Take(500)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -41,14 +41,14 @@ public class UnitOfWork : IUnitOfWork
|
||||
private IRepository<AiItemPrediction>? _aiItemPredictions;
|
||||
|
||||
// Powder Insights
|
||||
private IRepository<PowderUsageLog>? _powderUsageLogs;
|
||||
private IPowderUsageLogRepository? _powderUsageLogs;
|
||||
|
||||
// Core repositories
|
||||
private ICustomerRepository? _customers;
|
||||
private IJobRepository? _jobs;
|
||||
private IRepository<JobDailyPriority>? _jobDailyPriorities;
|
||||
private IRepository<JobItem>? _jobItems;
|
||||
private IRepository<JobItemCoat>? _jobItemCoats;
|
||||
private IJobItemCoatRepository? _jobItemCoats;
|
||||
private IRepository<JobItemPrepService>? _jobItemPrepServices;
|
||||
private IRepository<JobChangeHistory>? _jobChangeHistories;
|
||||
private IRepository<JobPrepService>? _jobPrepServices;
|
||||
@@ -59,13 +59,13 @@ public class UnitOfWork : IUnitOfWork
|
||||
private IRepository<QuoteItemPrepService>? _quoteItemPrepServices;
|
||||
private IRepository<QuoteChangeHistory>? _quoteChangeHistories;
|
||||
private IRepository<InventoryItem>? _inventoryItems;
|
||||
private IRepository<InventoryTransaction>? _inventoryTransactions;
|
||||
private IInventoryTransactionRepository? _inventoryTransactions;
|
||||
private IRepository<Equipment>? _equipment;
|
||||
private IRepository<OvenCost>? _ovenCosts;
|
||||
private IRepository<CompanyBlastSetup>? _blastSetups;
|
||||
private IRepository<MaintenanceRecord>? _maintenanceRecords;
|
||||
private IRepository<Vendor>? _vendors;
|
||||
private IRepository<JobPhoto>? _jobPhotos;
|
||||
private IJobPhotoRepository? _jobPhotos;
|
||||
private IRepository<JobNote>? _jobNotes;
|
||||
private IRepository<CustomerNote>? _customerNotes;
|
||||
private IRepository<JobStatusHistory>? _jobStatusHistory;
|
||||
@@ -97,13 +97,21 @@ public class UnitOfWork : IUnitOfWork
|
||||
private IRepository<SubscriptionPlanConfig>? _subscriptionPlanConfigs;
|
||||
|
||||
// Job Templates
|
||||
private IRepository<JobTemplate>? _jobTemplates;
|
||||
private IJobTemplateRepository? _jobTemplates;
|
||||
private IRepository<JobTemplateItem>? _jobTemplateItems;
|
||||
private IRepository<JobTemplateItemCoat>? _jobTemplateItemCoats;
|
||||
private IRepository<JobTemplateItemPrepService>? _jobTemplateItemPrepServices;
|
||||
|
||||
// Platform content
|
||||
private IPlainRepository<Announcement>? _announcements;
|
||||
private IPlainRepository<BannedIp>? _bannedIps;
|
||||
private IPlainRepository<DashboardTip>? _dashboardTips;
|
||||
private IRepository<InAppNotification>? _inAppNotifications;
|
||||
private IPlainRepository<ReleaseNote>? _releaseNotes;
|
||||
|
||||
// Bug Reports
|
||||
private IRepository<BugReport>? _bugReports;
|
||||
private IRepository<BugReportAttachment>? _bugReportAttachments;
|
||||
private IRepository<ContactSubmission>? _contactSubmissions;
|
||||
private IRepository<ManufacturerLookupPattern>? _manufacturerLookupPatterns;
|
||||
|
||||
@@ -169,8 +177,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
|
||||
// Powder Insights
|
||||
/// <summary>Repository for <see cref="PowderUsageLog"/> records capturing per-coat powder consumption; used by powder-usage analytics.</summary>
|
||||
public IRepository<PowderUsageLog> PowderUsageLogs =>
|
||||
_powderUsageLogs ??= new Repository<PowderUsageLog>(_context);
|
||||
public IPowderUsageLogRepository PowderUsageLogs =>
|
||||
_powderUsageLogs ??= new PowderUsageLogRepository(_context);
|
||||
|
||||
// Core repositories
|
||||
/// <summary>Repository for <see cref="Customer"/> records (commercial and non-commercial); tenant-filtered with soft delete.</summary>
|
||||
@@ -190,8 +198,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
_jobItems ??= new Repository<JobItem>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="JobItemCoat"/> powder coat passes; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<JobItemCoat> JobItemCoats =>
|
||||
_jobItemCoats ??= new Repository<JobItemCoat>(_context);
|
||||
public IJobItemCoatRepository JobItemCoats =>
|
||||
_jobItemCoats ??= new JobItemCoatRepository(_context);
|
||||
public IRepository<JobItemPrepService> JobItemPrepServices =>
|
||||
_jobItemPrepServices ??= new Repository<JobItemPrepService>(_context);
|
||||
|
||||
@@ -232,8 +240,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
_inventoryItems ??= new Repository<InventoryItem>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="InventoryTransaction"/> stock movements; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<InventoryTransaction> InventoryTransactions =>
|
||||
_inventoryTransactions ??= new Repository<InventoryTransaction>(_context);
|
||||
public IInventoryTransactionRepository InventoryTransactions =>
|
||||
_inventoryTransactions ??= new InventoryTransactionRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="Equipment"/> records (ovens, sandblasters, booths); tenant-filtered with soft delete.</summary>
|
||||
public IRepository<Equipment> Equipment =>
|
||||
@@ -256,8 +264,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
_vendors ??= new Repository<Vendor>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="JobPhoto"/> attachments; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<JobPhoto> JobPhotos =>
|
||||
_jobPhotos ??= new Repository<JobPhoto>(_context);
|
||||
public IJobPhotoRepository JobPhotos =>
|
||||
_jobPhotos ??= new JobPhotoRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="JobNote"/> free-text staff notes on jobs; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<JobNote> JobNotes =>
|
||||
@@ -372,11 +380,35 @@ public class UnitOfWork : IUnitOfWork
|
||||
public IRepository<SubscriptionPlanConfig> SubscriptionPlanConfigs =>
|
||||
_subscriptionPlanConfigs ??= new Repository<SubscriptionPlanConfig>(_context);
|
||||
|
||||
// Platform content
|
||||
/// <summary>Repository for <see cref="Announcement"/> platform-wide announcements; no tenant filter, no soft delete.</summary>
|
||||
public IPlainRepository<Announcement> Announcements =>
|
||||
_announcements ??= new PlainRepository<Announcement>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="BannedIp"/> IP ban records; no tenant filter, no soft delete.</summary>
|
||||
public IPlainRepository<BannedIp> BannedIps =>
|
||||
_bannedIps ??= new PlainRepository<BannedIp>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="DashboardTip"/> rotating tip-of-the-day entries; no tenant filter, no soft delete.</summary>
|
||||
public IPlainRepository<DashboardTip> DashboardTips =>
|
||||
_dashboardTips ??= new PlainRepository<DashboardTip>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="InAppNotification"/> bell-notification records; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<InAppNotification> InAppNotifications =>
|
||||
_inAppNotifications ??= new Repository<InAppNotification>(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="ReleaseNote"/> platform changelog entries; no tenant filter, no soft delete.</summary>
|
||||
public IPlainRepository<ReleaseNote> ReleaseNotes =>
|
||||
_releaseNotes ??= new PlainRepository<ReleaseNote>(_context);
|
||||
|
||||
// Bug Reports
|
||||
/// <summary>Repository for <see cref="BugReport"/> user-submitted bug reports; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<BugReport> BugReports =>
|
||||
_bugReports ??= new Repository<BugReport>(_context);
|
||||
|
||||
public IRepository<BugReportAttachment> BugReportAttachments =>
|
||||
_bugReportAttachments ??= new Repository<BugReportAttachment>(_context);
|
||||
|
||||
// Contact Us
|
||||
/// <summary>Repository for <see cref="ContactSubmission"/> contact form submissions; platform admins see all, company users see their own.</summary>
|
||||
public IRepository<ContactSubmission> ContactSubmissions =>
|
||||
@@ -397,8 +429,8 @@ public class UnitOfWork : IUnitOfWork
|
||||
|
||||
// Job Templates
|
||||
/// <summary>Repository for <see cref="JobTemplate"/> reusable job blueprints; tenant-filtered with soft delete.</summary>
|
||||
public IRepository<JobTemplate> JobTemplates =>
|
||||
_jobTemplates ??= new Repository<JobTemplate>(_context);
|
||||
public IJobTemplateRepository JobTemplates =>
|
||||
_jobTemplates ??= new JobTemplateRepository(_context);
|
||||
|
||||
/// <summary>Repository for <see cref="JobTemplateItem"/> item definitions within a job template.</summary>
|
||||
public IRepository<JobTemplateItem> JobTemplateItems =>
|
||||
|
||||
Reference in New Issue
Block a user