Add explicit CompanyId to tenant-scoped FindAsync queries (partial sweep)
Multi-tenant defense-in-depth sweep, FindAsync/FirstOrDefaultAsync vector. Adds explicit CompanyId predicates to list/index/validation queries that previously relied only on the global tenant filter (exposure: raw platform-admin sessions where the filter is bypassed). Done this pass: - Financial: Budgets, CreditMemos, FixedAssets, GiftCertificates, TaxRates, PricingTiers, VendorCredits, Accounts (year-end close), Invoices (tax-rate default, merchandise). - Operational: Inventory (bin/sample-panels/vendors/usage-edit), OvenScheduler (ovens/batches/queue), Customers (pricing tiers), InAppNotifications (mark-all-read), CatalogItems (by-category / merchandise / price-check lists). - AI: AiQuickQuote and Quotes (powder cost, predictions, walk-in customer, benchmark), Reports (budgets, 1099 vendors). Child-by-parent-FK and by-PK queries were left as-is (already scoped via the verified parent). Builds clean; 293 unit tests pass. REMAINING (next session): ReportsController.Analytics powder-usage query (line ~593) and the ~20 CompanySettings delete-protection Count/Any + dup-code checks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -474,7 +474,7 @@ public class AccountsController : Controller
|
|||||||
public async Task<IActionResult> YearEndClose()
|
public async Task<IActionResult> YearEndClose()
|
||||||
{
|
{
|
||||||
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var history = (await _unitOfWork.YearEndCloses.FindAsync(y => true, false, y => y.JournalEntry))
|
var history = (await _unitOfWork.YearEndCloses.FindAsync(y => y.CompanyId == companyId, false, y => y.JournalEntry))
|
||||||
.OrderByDescending(y => y.ClosedYear)
|
.OrderByDescending(y => y.ClosedYear)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ public class AccountsController : Controller
|
|||||||
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
|
||||||
// Idempotency check
|
// Idempotency check
|
||||||
var existing = (await _unitOfWork.YearEndCloses.FindAsync(y => y.ClosedYear == year)).FirstOrDefault();
|
var existing = (await _unitOfWork.YearEndCloses.FindAsync(y => y.CompanyId == companyId && y.ClosedYear == year)).FirstOrDefault();
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
TempData["Error"] = $"{year} has already been closed (JE {existing.JournalEntryId}).";
|
TempData["Error"] = $"{year} has already been closed (JE {existing.JournalEntryId}).";
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class AiQuickQuoteController : Controller
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var powders = await _unitOfWork.InventoryItems.FindAsync(i =>
|
var powders = await _unitOfWork.InventoryItems.FindAsync(i =>
|
||||||
i.Category != null && i.Category.ToLower().Contains("powder") && i.UnitCost > 0);
|
i.CompanyId == currentUser.CompanyId && i.Category != null && i.Category.ToLower().Contains("powder") && i.UnitCost > 0);
|
||||||
if (powders.Any())
|
if (powders.Any())
|
||||||
avgPowderCost = powders.Average(p => p.UnitCost);
|
avgPowderCost = powders.Average(p => p.UnitCost);
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ public class AiQuickQuoteController : Controller
|
|||||||
var context = new CompanyAiContext { ProfileText = costs.AiContextProfile };
|
var context = new CompanyAiContext { ProfileText = costs.AiContextProfile };
|
||||||
|
|
||||||
var predictions = await _unitOfWork.AiItemPredictions.FindAsync(
|
var predictions = await _unitOfWork.AiItemPredictions.FindAsync(
|
||||||
p => !p.UserOverrodeEstimate && p.PredictedSurfaceAreaSqFt > 0 && p.PredictedUnitPrice > 0);
|
p => p.CompanyId == companyId && !p.UserOverrodeEstimate && p.PredictedSurfaceAreaSqFt > 0 && p.PredictedUnitPrice > 0);
|
||||||
|
|
||||||
context.AcceptedExamples = predictions
|
context.AcceptedExamples = predictions
|
||||||
.OrderByDescending(p => p.CreatedAt)
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
@@ -213,8 +213,9 @@ public class AiQuickQuoteController : Controller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var companyId = (await _userManager.GetUserAsync(User))?.CompanyId ?? 0;
|
||||||
var inventory = await _unitOfWork.InventoryItems.FindAsync(
|
var inventory = await _unitOfWork.InventoryItems.FindAsync(
|
||||||
i => i.IsActive,
|
i => i.CompanyId == companyId && i.IsActive,
|
||||||
false,
|
false,
|
||||||
i => i.InventoryCategory);
|
i => i.InventoryCategory);
|
||||||
|
|
||||||
@@ -267,7 +268,7 @@ public class AiQuickQuoteController : Controller
|
|||||||
private async Task<Customer> GetOrCreateWalkInCustomerAsync(int companyId)
|
private async Task<Customer> GetOrCreateWalkInCustomerAsync(int companyId)
|
||||||
{
|
{
|
||||||
var existing = (await _unitOfWork.Customers.FindAsync(
|
var existing = (await _unitOfWork.Customers.FindAsync(
|
||||||
c => c.CompanyName == "Walk-In / Phone" && c.IsActive))
|
c => c.CompanyId == companyId && c.CompanyName == "Walk-In / Phone" && c.IsActive))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (existing != null) return existing;
|
if (existing != null) return existing;
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ public class BudgetsController : Controller
|
|||||||
/// <summary>Lists all budgets for the current company ordered by fiscal year descending.</summary>
|
/// <summary>Lists all budgets for the current company ordered by fiscal year descending.</summary>
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
var budgets = (await _unitOfWork.Budgets.FindAsync(b => true, false, b => b.Lines))
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
var budgets = (await _unitOfWork.Budgets.FindAsync(b => b.CompanyId == companyId, false, b => b.Lines))
|
||||||
.OrderByDescending(b => b.FiscalYear)
|
.OrderByDescending(b => b.FiscalYear)
|
||||||
.ThenBy(b => b.Name)
|
.ThenBy(b => b.Name)
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -255,7 +256,7 @@ public class BudgetsController : Controller
|
|||||||
private async Task ClearDefaultFlagAsync(int companyId, int fiscalYear, int? excludeId)
|
private async Task ClearDefaultFlagAsync(int companyId, int fiscalYear, int? excludeId)
|
||||||
{
|
{
|
||||||
var others = await _unitOfWork.Budgets.FindAsync(
|
var others = await _unitOfWork.Budgets.FindAsync(
|
||||||
b => b.IsDefault && b.FiscalYear == fiscalYear && b.Id != (excludeId ?? 0));
|
b => b.CompanyId == companyId && b.IsDefault && b.FiscalYear == fiscalYear && b.Id != (excludeId ?? 0));
|
||||||
foreach (var b in others)
|
foreach (var b in others)
|
||||||
{
|
{
|
||||||
b.IsDefault = false;
|
b.IsDefault = false;
|
||||||
|
|||||||
@@ -500,7 +500,8 @@ namespace PowderCoating.Web.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var items = await _unitOfWork.CatalogItems.FindAsync(i => i.CategoryId == categoryId && i.IsActive);
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
var items = await _unitOfWork.CatalogItems.FindAsync(i => i.CompanyId == companyId && i.CategoryId == categoryId && i.IsActive);
|
||||||
|
|
||||||
var itemDtos = items
|
var itemDtos = items
|
||||||
.OrderBy(i => i.DisplayOrder)
|
.OrderBy(i => i.DisplayOrder)
|
||||||
@@ -541,8 +542,9 @@ namespace PowderCoating.Web.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var items = await _unitOfWork.CatalogItems.FindAsync(
|
var items = await _unitOfWork.CatalogItems.FindAsync(
|
||||||
i => i.IsMerchandise && i.IsActive, false, i => i.Category);
|
i => i.CompanyId == companyId && i.IsMerchandise && i.IsActive, false, i => i.Category);
|
||||||
|
|
||||||
var result = items
|
var result = items
|
||||||
.OrderBy(i => i.Category.Name)
|
.OrderBy(i => i.Category.Name)
|
||||||
@@ -912,7 +914,7 @@ namespace PowderCoating.Web.Controllers
|
|||||||
|
|
||||||
// Get all active catalog items with their categories
|
// Get all active catalog items with their categories
|
||||||
var items = await _unitOfWork.CatalogItems.FindAsync(
|
var items = await _unitOfWork.CatalogItems.FindAsync(
|
||||||
ci => ci.IsActive,
|
ci => ci.CompanyId == currentUser.CompanyId && ci.IsActive,
|
||||||
false,
|
false,
|
||||||
ci => ci.Category
|
ci => ci.Category
|
||||||
);
|
);
|
||||||
@@ -967,7 +969,7 @@ namespace PowderCoating.Web.Controllers
|
|||||||
r => r.CompanyId == currentUser.CompanyId);
|
r => r.CompanyId == currentUser.CompanyId);
|
||||||
var report = existing.OrderByDescending(r => r.RunAt).FirstOrDefault();
|
var report = existing.OrderByDescending(r => r.RunAt).FirstOrDefault();
|
||||||
|
|
||||||
var pricedItems = await _unitOfWork.CatalogItems.FindAsync(ci => ci.IsActive && ci.DefaultPrice > 0);
|
var pricedItems = await _unitOfWork.CatalogItems.FindAsync(ci => ci.CompanyId == currentUser.CompanyId && ci.IsActive && ci.DefaultPrice > 0);
|
||||||
ViewBag.ActiveItemCount = pricedItems.Count();
|
ViewBag.ActiveItemCount = pricedItems.Count();
|
||||||
|
|
||||||
if (report != null)
|
if (report != null)
|
||||||
@@ -1051,7 +1053,7 @@ namespace PowderCoating.Web.Controllers
|
|||||||
// Load active catalog items with a real price — skip $0 items (placeholders,
|
// Load active catalog items with a real price — skip $0 items (placeholders,
|
||||||
// category headers, etc.) since there's no pricing to evaluate.
|
// category headers, etc.) since there's no pricing to evaluate.
|
||||||
var items = (await _unitOfWork.CatalogItems.FindAsync(
|
var items = (await _unitOfWork.CatalogItems.FindAsync(
|
||||||
ci => ci.IsActive && ci.DefaultPrice > 0, false, ci => ci.Category)).ToList();
|
ci => ci.CompanyId == currentUser.CompanyId && ci.IsActive && ci.DefaultPrice > 0, false, ci => ci.Category)).ToList();
|
||||||
|
|
||||||
if (items.Count == 0)
|
if (items.Count == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ public class CreditMemosController : Controller
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Index(string? status, string? search)
|
public async Task<IActionResult> Index(string? status, string? search)
|
||||||
{
|
{
|
||||||
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var memos = await _unitOfWork.CreditMemos.FindAsync(
|
var memos = await _unitOfWork.CreditMemos.FindAsync(
|
||||||
m => true, false,
|
m => m.CompanyId == companyId, false,
|
||||||
m => m.Customer);
|
m => m.Customer);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(search))
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
|||||||
@@ -1411,7 +1411,8 @@ public class CustomersController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task PopulatePricingTiersAsync()
|
private async Task PopulatePricingTiersAsync()
|
||||||
{
|
{
|
||||||
var tiers = await _unitOfWork.PricingTiers.FindAsync(t => t.IsActive);
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
var tiers = await _unitOfWork.PricingTiers.FindAsync(t => t.CompanyId == companyId && t.IsActive);
|
||||||
ViewBag.PricingTiers = tiers
|
ViewBag.PricingTiers = tiers
|
||||||
.OrderBy(t => t.TierName)
|
.OrderBy(t => t.TierName)
|
||||||
.Select(t => new SelectListItem
|
.Select(t => new SelectListItem
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ public class FixedAssetsController : Controller
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var assets = await _unitOfWork.FixedAssets.FindAsync(
|
var assets = await _unitOfWork.FixedAssets.FindAsync(
|
||||||
fa => true, false,
|
fa => fa.CompanyId == companyId, false,
|
||||||
fa => fa.AssetAccount,
|
fa => fa.AssetAccount,
|
||||||
fa => fa.DepreciationExpenseAccount,
|
fa => fa.DepreciationExpenseAccount,
|
||||||
fa => fa.AccumDepreciationAccount);
|
fa => fa.AccumDepreciationAccount);
|
||||||
@@ -192,7 +193,7 @@ public class FixedAssetsController : Controller
|
|||||||
var currentUser = await _userManager.GetUserAsync(User);
|
var currentUser = await _userManager.GetUserAsync(User);
|
||||||
|
|
||||||
var assets = await _unitOfWork.FixedAssets.FindAsync(
|
var assets = await _unitOfWork.FixedAssets.FindAsync(
|
||||||
fa => !fa.IsDisposed, false,
|
fa => fa.CompanyId == companyId && !fa.IsDisposed, false,
|
||||||
fa => fa.DepreciationEntries);
|
fa => fa.DepreciationEntries);
|
||||||
|
|
||||||
int posted = 0, skipped = 0;
|
int posted = 0, skipped = 0;
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ public class GiftCertificatesController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IActionResult> Index(string? searchTerm, string? statusFilter)
|
public async Task<IActionResult> Index(string? searchTerm, string? statusFilter)
|
||||||
{
|
{
|
||||||
|
var companyId = (await _userManager.GetUserAsync(User))?.CompanyId ?? 0;
|
||||||
var certs = await _unitOfWork.GiftCertificates.FindAsync(
|
var certs = await _unitOfWork.GiftCertificates.FindAsync(
|
||||||
gc => true, false,
|
gc => gc.CompanyId == companyId, false,
|
||||||
gc => gc.RecipientCustomer,
|
gc => gc.RecipientCustomer,
|
||||||
gc => gc.PurchasingCustomer);
|
gc => gc.PurchasingCustomer);
|
||||||
|
|
||||||
@@ -420,7 +421,8 @@ public class GiftCertificatesController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task PopulateCustomersAsync()
|
private async Task PopulateCustomersAsync()
|
||||||
{
|
{
|
||||||
var customers = await _unitOfWork.Customers.FindAsync(c => c.IsActive);
|
var companyId = (await _userManager.GetUserAsync(User))?.CompanyId ?? 0;
|
||||||
|
var customers = await _unitOfWork.Customers.FindAsync(c => c.CompanyId == companyId && c.IsActive);
|
||||||
var list = customers
|
var list = customers
|
||||||
.OrderBy(c => c.CompanyName ?? c.ContactFirstName)
|
.OrderBy(c => c.CompanyName ?? c.ContactFirstName)
|
||||||
.Select(c => new SelectListItem
|
.Select(c => new SelectListItem
|
||||||
|
|||||||
@@ -126,11 +126,12 @@ public class InAppNotificationsController : Controller
|
|||||||
public async Task<IActionResult> MarkAllRead()
|
public async Task<IActionResult> MarkAllRead()
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
var companyId = _tenant.GetCurrentCompanyId() ?? 0;
|
||||||
|
|
||||||
var unread = _tenant.IsPlatformAdmin()
|
var unread = _tenant.IsPlatformAdmin()
|
||||||
? (await _unitOfWork.InAppNotifications.FindAsync(
|
? (await _unitOfWork.InAppNotifications.FindAsync(
|
||||||
n => !n.IsDeleted && n.CompanyId == 0 && !n.IsRead, ignoreQueryFilters: true)).ToList()
|
n => !n.IsDeleted && n.CompanyId == 0 && !n.IsRead, ignoreQueryFilters: true)).ToList()
|
||||||
: (await _unitOfWork.InAppNotifications.FindAsync(n => !n.IsRead)).ToList();
|
: (await _unitOfWork.InAppNotifications.FindAsync(n => n.CompanyId == companyId && !n.IsRead)).ToList();
|
||||||
|
|
||||||
foreach (var n in unread)
|
foreach (var n in unread)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -193,8 +193,9 @@ public class InventoryController : Controller
|
|||||||
return RedirectToAction(nameof(Index));
|
return RedirectToAction(nameof(Index));
|
||||||
|
|
||||||
var loc = location.Trim();
|
var loc = location.Trim();
|
||||||
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var items = await _unitOfWork.InventoryItems.FindAsync(
|
var items = await _unitOfWork.InventoryItems.FindAsync(
|
||||||
i => i.Location != null && i.Location.ToLower() == loc.ToLower());
|
i => i.CompanyId == companyId && i.Location != null && i.Location.ToLower() == loc.ToLower());
|
||||||
|
|
||||||
var dtos = _mapper.Map<List<InventoryListDto>>(items.OrderBy(i => i.Name).ToList());
|
var dtos = _mapper.Map<List<InventoryListDto>>(items.OrderBy(i => i.Name).ToList());
|
||||||
ViewBag.Location = loc;
|
ViewBag.Location = loc;
|
||||||
@@ -1531,8 +1532,9 @@ public class InventoryController : Controller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var allCoatings = (await _unitOfWork.InventoryItems.FindAsync(
|
var allCoatings = (await _unitOfWork.InventoryItems.FindAsync(
|
||||||
i => i.InventoryCategory != null && i.InventoryCategory.IsCoating,
|
i => i.CompanyId == companyId && i.InventoryCategory != null && i.InventoryCategory.IsCoating,
|
||||||
false,
|
false,
|
||||||
i => i.InventoryCategory))
|
i => i.InventoryCategory))
|
||||||
.OrderBy(i => i.Manufacturer).ThenBy(i => i.ColorName).ThenBy(i => i.Name)
|
.OrderBy(i => i.Manufacturer).ThenBy(i => i.ColorName).ThenBy(i => i.Name)
|
||||||
@@ -1609,7 +1611,7 @@ public class InventoryController : Controller
|
|||||||
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
ViewBag.AiInventoryAssistEnabled = await _subscriptionService.IsAiInventoryAssistEnabledAsync(companyId);
|
ViewBag.AiInventoryAssistEnabled = await _subscriptionService.IsAiInventoryAssistEnabledAsync(companyId);
|
||||||
|
|
||||||
var vendors = (await _unitOfWork.Vendors.FindAsync(v => v.IsActive, false, v => v.Categories))
|
var vendors = (await _unitOfWork.Vendors.FindAsync(v => v.CompanyId == companyId && v.IsActive, false, v => v.Categories))
|
||||||
.OrderBy(v => v.CompanyName).ToList();
|
.OrderBy(v => v.CompanyName).ToList();
|
||||||
ViewBag.Vendors = new SelectList(vendors, "Id", "CompanyName");
|
ViewBag.Vendors = new SelectList(vendors, "Id", "CompanyName");
|
||||||
|
|
||||||
@@ -2225,7 +2227,7 @@ public class InventoryController : Controller
|
|||||||
return BadRequest("Only usage transactions can be edited here.");
|
return BadRequest("Only usage transactions can be edited here.");
|
||||||
|
|
||||||
var allJobs = await _unitOfWork.Jobs.FindAsync(
|
var allJobs = await _unitOfWork.Jobs.FindAsync(
|
||||||
j => !j.JobStatus.IsTerminalStatus,
|
j => j.CompanyId == txn.CompanyId && !j.JobStatus.IsTerminalStatus,
|
||||||
false,
|
false,
|
||||||
j => j.Customer,
|
j => j.Customer,
|
||||||
j => j.JobStatus);
|
j => j.JobStatus);
|
||||||
|
|||||||
@@ -2413,7 +2413,7 @@ public class InvoicesController : Controller
|
|||||||
return Json(new { taxPercent = 0m, taxRateName = (string?)null });
|
return Json(new { taxPercent = 0m, taxRateName = (string?)null });
|
||||||
|
|
||||||
var defaultRate = await _unitOfWork.TaxRates
|
var defaultRate = await _unitOfWork.TaxRates
|
||||||
.FirstOrDefaultAsync(r => r.IsDefault && r.IsActive && !r.IsDeleted);
|
.FirstOrDefaultAsync(r => r.CompanyId == customer.CompanyId && r.IsDefault && r.IsActive && !r.IsDeleted);
|
||||||
|
|
||||||
return Json(new
|
return Json(new
|
||||||
{
|
{
|
||||||
@@ -2450,7 +2450,7 @@ public class InvoicesController : Controller
|
|||||||
|
|
||||||
// Merchandise items for the invoice merch picker (all active IsMerchandise items)
|
// Merchandise items for the invoice merch picker (all active IsMerchandise items)
|
||||||
var allMerchItems = await _unitOfWork.CatalogItems.FindAsync(
|
var allMerchItems = await _unitOfWork.CatalogItems.FindAsync(
|
||||||
i => i.IsMerchandise && i.IsActive, false, i => i.Category);
|
i => i.CompanyId == companyId && i.IsMerchandise && i.IsActive, false, i => i.Category);
|
||||||
var merchItems = allMerchItems
|
var merchItems = allMerchItems
|
||||||
.OrderBy(i => i.Category.Name).ThenBy(i => i.DisplayOrder).ThenBy(i => i.Name)
|
.OrderBy(i => i.Category.Name).ThenBy(i => i.DisplayOrder).ThenBy(i => i.Name)
|
||||||
.Select(i => new { i.Id, i.Name, i.SKU, CategoryName = i.Category.Name, i.DefaultPrice, i.RevenueAccountId })
|
.Select(i => new { i.Id, i.Name, i.SKU, CategoryName = i.Category.Name, i.DefaultPrice, i.RevenueAccountId })
|
||||||
|
|||||||
@@ -61,16 +61,17 @@ public class OvenSchedulerController : Controller
|
|||||||
public async Task<IActionResult> Index(DateTime? date, string goal = "maximize_throughput")
|
public async Task<IActionResult> Index(DateTime? date, string goal = "maximize_throughput")
|
||||||
{
|
{
|
||||||
var scheduledDate = date?.Date ?? DateTime.Today;
|
var scheduledDate = date?.Date ?? DateTime.Today;
|
||||||
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
|
||||||
// Load active Named Ovens — filter IsActive at database level
|
// Load active Named Ovens — filter IsActive at database level
|
||||||
var ovenCosts = (await _unitOfWork.OvenCosts.FindAsync(o => o.IsActive))
|
var ovenCosts = (await _unitOfWork.OvenCosts.FindAsync(o => o.CompanyId == companyId && o.IsActive))
|
||||||
.OrderBy(o => o.DisplayOrder).ThenBy(o => o.Label)
|
.OrderBy(o => o.DisplayOrder).ThenBy(o => o.Label)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Load batches for the selected date — filter at database level with includes
|
// Load batches for the selected date — filter at database level with includes
|
||||||
var scheduledDateEnd = scheduledDate.AddDays(1);
|
var scheduledDateEnd = scheduledDate.AddDays(1);
|
||||||
var batches = (await _unitOfWork.OvenBatches.FindAsync(
|
var batches = (await _unitOfWork.OvenBatches.FindAsync(
|
||||||
b => b.ScheduledDate >= scheduledDate && b.ScheduledDate < scheduledDateEnd
|
b => b.CompanyId == companyId && b.ScheduledDate >= scheduledDate && b.ScheduledDate < scheduledDateEnd
|
||||||
&& b.Status != OvenBatchStatus.Cancelled,
|
&& b.Status != OvenBatchStatus.Cancelled,
|
||||||
false,
|
false,
|
||||||
b => b.OvenCost, b => b.Items))
|
b => b.OvenCost, b => b.Items))
|
||||||
@@ -98,7 +99,7 @@ public class OvenSchedulerController : Controller
|
|||||||
|
|
||||||
// Load jobs in the queue — filter by status at database level
|
// Load jobs in the queue — filter by status at database level
|
||||||
var queueJobs = (await _unitOfWork.Jobs.FindAsync(
|
var queueJobs = (await _unitOfWork.Jobs.FindAsync(
|
||||||
j => j.JobStatus != null && QueueableStatuses.Contains(j.JobStatus.StatusCode),
|
j => j.CompanyId == companyId && j.JobStatus != null && QueueableStatuses.Contains(j.JobStatus.StatusCode),
|
||||||
false,
|
false,
|
||||||
j => j.Customer, j => j.JobStatus, j => j.JobPriority, j => j.JobItems))
|
j => j.Customer, j => j.JobStatus, j => j.JobPriority, j => j.JobItems))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ public class PricingTiersController : Controller
|
|||||||
return View(dto);
|
return View(dto);
|
||||||
|
|
||||||
// Check for duplicate name
|
// Check for duplicate name
|
||||||
var existing = await _unitOfWork.PricingTiers.FindAsync(t => t.TierName == dto.TierName);
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
var existing = await _unitOfWork.PricingTiers.FindAsync(t => t.CompanyId == companyId && t.TierName == dto.TierName);
|
||||||
if (existing.Any())
|
if (existing.Any())
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(dto.TierName), "A tier with this name already exists.");
|
ModelState.AddModelError(nameof(dto.TierName), "A tier with this name already exists.");
|
||||||
@@ -111,8 +112,9 @@ public class PricingTiersController : Controller
|
|||||||
if (entity == null) return NotFound();
|
if (entity == null) return NotFound();
|
||||||
|
|
||||||
// Check for duplicate name (excluding this record)
|
// Check for duplicate name (excluding this record)
|
||||||
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var duplicate = await _unitOfWork.PricingTiers.FindAsync(
|
var duplicate = await _unitOfWork.PricingTiers.FindAsync(
|
||||||
t => t.TierName == dto.TierName && t.Id != dto.Id);
|
t => t.CompanyId == companyId && t.TierName == dto.TierName && t.Id != dto.Id);
|
||||||
if (duplicate.Any())
|
if (duplicate.Any())
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(dto.TierName), "A tier with this name already exists.");
|
ModelState.AddModelError(nameof(dto.TierName), "A tier with this name already exists.");
|
||||||
@@ -138,7 +140,8 @@ public class PricingTiersController : Controller
|
|||||||
if (entity == null) return NotFound();
|
if (entity == null) return NotFound();
|
||||||
|
|
||||||
// Block delete if customers are assigned to this tier
|
// Block delete if customers are assigned to this tier
|
||||||
var assignedCustomers = await _unitOfWork.Customers.FindAsync(c => c.PricingTierId == id);
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
var assignedCustomers = await _unitOfWork.Customers.FindAsync(c => c.CompanyId == companyId && c.PricingTierId == id);
|
||||||
if (assignedCustomers.Any())
|
if (assignedCustomers.Any())
|
||||||
{
|
{
|
||||||
TempData["ErrorMessage"] = $"Cannot delete '{entity.TierName}' — {assignedCustomers.Count()} customer(s) are assigned to it. Reassign them first.";
|
TempData["ErrorMessage"] = $"Cannot delete '{entity.TierName}' — {assignedCustomers.Count()} customer(s) are assigned to it. Reassign them first.";
|
||||||
|
|||||||
@@ -3428,7 +3428,7 @@ public class QuotesController : Controller
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var powders = await _unitOfWork.InventoryItems.FindAsync(i =>
|
var powders = await _unitOfWork.InventoryItems.FindAsync(i =>
|
||||||
i.Category != null && i.Category.ToLower().Contains("powder") && i.UnitCost > 0);
|
i.CompanyId == companyId && i.Category != null && i.Category.ToLower().Contains("powder") && i.UnitCost > 0);
|
||||||
avgPowderCost = powders.Any() ? powders.Average(p => p.UnitCost) : 8m;
|
avgPowderCost = powders.Any() ? powders.Average(p => p.UnitCost) : 8m;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -3522,7 +3522,7 @@ public class QuotesController : Controller
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var powders = await _unitOfWork.InventoryItems.FindAsync(i =>
|
var powders = await _unitOfWork.InventoryItems.FindAsync(i =>
|
||||||
i.Category != null && i.Category.ToLower().Contains("powder") && i.UnitCost > 0);
|
i.CompanyId == companyId && i.Category != null && i.Category.ToLower().Contains("powder") && i.UnitCost > 0);
|
||||||
avgPowderCost = powders.Any() ? powders.Average(p => p.UnitCost) : 8m;
|
avgPowderCost = powders.Any() ? powders.Average(p => p.UnitCost) : 8m;
|
||||||
}
|
}
|
||||||
catch { avgPowderCost = 8m; }
|
catch { avgPowderCost = 8m; }
|
||||||
@@ -3617,7 +3617,7 @@ public class QuotesController : Controller
|
|||||||
|
|
||||||
// Pull recent accepted predictions (user didn't override) as few-shot calibration examples
|
// Pull recent accepted predictions (user didn't override) as few-shot calibration examples
|
||||||
var allPredictions = await _unitOfWork.AiItemPredictions.FindAsync(
|
var allPredictions = await _unitOfWork.AiItemPredictions.FindAsync(
|
||||||
p => !p.UserOverrodeEstimate && p.PredictedSurfaceAreaSqFt > 0 && p.PredictedUnitPrice > 0);
|
p => p.CompanyId == companyId && !p.UserOverrodeEstimate && p.PredictedSurfaceAreaSqFt > 0 && p.PredictedUnitPrice > 0);
|
||||||
|
|
||||||
context.AcceptedExamples = allPredictions
|
context.AcceptedExamples = allPredictions
|
||||||
.OrderByDescending(p => p.CreatedAt)
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
@@ -3660,9 +3660,11 @@ public class QuotesController : Controller
|
|||||||
{
|
{
|
||||||
var sqFtMin = sqFt * 0.4m;
|
var sqFtMin = sqFt * 0.4m;
|
||||||
var sqFtMax = sqFt * 2.5m;
|
var sqFtMax = sqFt * 2.5m;
|
||||||
|
var companyId = (await _userManager.GetUserAsync(User))?.CompanyId ?? 0;
|
||||||
|
|
||||||
var matches = await _unitOfWork.JobItems.FindAsync(
|
var matches = await _unitOfWork.JobItems.FindAsync(
|
||||||
ji => ji.Complexity == complexity
|
ji => ji.CompanyId == companyId
|
||||||
|
&& ji.Complexity == complexity
|
||||||
&& ji.SurfaceAreaSqFt >= sqFtMin
|
&& ji.SurfaceAreaSqFt >= sqFtMin
|
||||||
&& ji.SurfaceAreaSqFt <= sqFtMax
|
&& ji.SurfaceAreaSqFt <= sqFtMax
|
||||||
&& ji.UnitPrice > 0
|
&& ji.UnitPrice > 0
|
||||||
@@ -3670,7 +3672,7 @@ public class QuotesController : Controller
|
|||||||
|
|
||||||
var jobIds = matches.Select(ji => ji.JobId).Distinct().ToList();
|
var jobIds = matches.Select(ji => ji.JobId).Distinct().ToList();
|
||||||
var completedStatusIds = (await _unitOfWork.JobStatusLookups.FindAsync(
|
var completedStatusIds = (await _unitOfWork.JobStatusLookups.FindAsync(
|
||||||
s => s.StatusCode == AppConstants.StatusCodes.Job.Completed || s.StatusCode == AppConstants.StatusCodes.Job.Delivered))
|
s => s.CompanyId == companyId && (s.StatusCode == AppConstants.StatusCodes.Job.Completed || s.StatusCode == AppConstants.StatusCodes.Job.Delivered)))
|
||||||
.Select(s => s.Id).ToHashSet();
|
.Select(s => s.Id).ToHashSet();
|
||||||
var completedJobs = await _unitOfWork.Jobs.FindAsync(
|
var completedJobs = await _unitOfWork.Jobs.FindAsync(
|
||||||
j => jobIds.Contains(j.Id) && completedStatusIds.Contains(j.JobStatusId));
|
j => jobIds.Contains(j.Id) && completedStatusIds.Contains(j.JobStatusId));
|
||||||
|
|||||||
@@ -2512,7 +2512,7 @@ public class ReportsController : Controller
|
|||||||
var reportYear = year ?? DateTime.Now.Year;
|
var reportYear = year ?? DateTime.Now.Year;
|
||||||
|
|
||||||
// Load all budgets for the year for the selector
|
// Load all budgets for the year for the selector
|
||||||
var allBudgets = (await _unitOfWork.Budgets.FindAsync(b => b.FiscalYear == reportYear))
|
var allBudgets = (await _unitOfWork.Budgets.FindAsync(b => b.CompanyId == companyId && b.FiscalYear == reportYear))
|
||||||
.OrderBy(b => b.Name).ToList();
|
.OrderBy(b => b.Name).ToList();
|
||||||
|
|
||||||
Core.Entities.Budget? budget = null;
|
Core.Entities.Budget? budget = null;
|
||||||
@@ -2520,10 +2520,10 @@ public class ReportsController : Controller
|
|||||||
budget = await _unitOfWork.Budgets.GetByIdAsync(budgetId.Value, false, b => b.Lines);
|
budget = await _unitOfWork.Budgets.GetByIdAsync(budgetId.Value, false, b => b.Lines);
|
||||||
|
|
||||||
budget ??= (await _unitOfWork.Budgets.FindAsync(
|
budget ??= (await _unitOfWork.Budgets.FindAsync(
|
||||||
b => b.FiscalYear == reportYear && b.IsDefault, false, b => b.Lines)).FirstOrDefault();
|
b => b.CompanyId == companyId && b.FiscalYear == reportYear && b.IsDefault, false, b => b.Lines)).FirstOrDefault();
|
||||||
|
|
||||||
budget ??= (await _unitOfWork.Budgets.FindAsync(
|
budget ??= (await _unitOfWork.Budgets.FindAsync(
|
||||||
b => b.FiscalYear == reportYear, false, b => b.Lines)).FirstOrDefault();
|
b => b.CompanyId == companyId && b.FiscalYear == reportYear, false, b => b.Lines)).FirstOrDefault();
|
||||||
|
|
||||||
ViewBag.ReportYear = reportYear;
|
ViewBag.ReportYear = reportYear;
|
||||||
ViewBag.Budget = budget;
|
ViewBag.Budget = budget;
|
||||||
@@ -2599,7 +2599,7 @@ public class ReportsController : Controller
|
|||||||
var periodEnd = new DateTime(reportYear, 12, 31, 23, 59, 59, DateTimeKind.Utc);
|
var periodEnd = new DateTime(reportYear, 12, 31, 23, 59, 59, DateTimeKind.Utc);
|
||||||
|
|
||||||
// Load 1099-eligible vendors
|
// Load 1099-eligible vendors
|
||||||
var vendors = (await _unitOfWork.Vendors.FindAsync(v => v.Is1099Vendor)).ToList();
|
var vendors = (await _unitOfWork.Vendors.FindAsync(v => v.CompanyId == companyId && v.Is1099Vendor)).ToList();
|
||||||
|
|
||||||
var rows = new List<Vendor1099Row>();
|
var rows = new List<Vendor1099Row>();
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ public class TaxRatesController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ClearOtherDefaultsAsync(int exceptId)
|
private async Task ClearOtherDefaultsAsync(int exceptId)
|
||||||
{
|
{
|
||||||
var others = await _unitOfWork.TaxRates.FindAsync(r => r.IsDefault && r.Id != exceptId);
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
|
var others = await _unitOfWork.TaxRates.FindAsync(r => r.CompanyId == companyId && r.IsDefault && r.Id != exceptId);
|
||||||
foreach (var r in others)
|
foreach (var r in others)
|
||||||
r.IsDefault = false;
|
r.IsDefault = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ public class VendorCreditsController : Controller
|
|||||||
private async Task PopulateDropdownsAsync()
|
private async Task PopulateDropdownsAsync()
|
||||||
{
|
{
|
||||||
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
||||||
var vendors = await _unitOfWork.Vendors.FindAsync(v => v.IsActive);
|
var vendors = await _unitOfWork.Vendors.FindAsync(v => v.CompanyId == companyId && v.IsActive);
|
||||||
var accounts = await _unitOfWork.Accounts.FindAsync(a => a.CompanyId == companyId && a.IsActive);
|
var accounts = await _unitOfWork.Accounts.FindAsync(a => a.CompanyId == companyId && a.IsActive);
|
||||||
|
|
||||||
ViewBag.VendorList = vendors
|
ViewBag.VendorList = vendors
|
||||||
|
|||||||
Reference in New Issue
Block a user