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:
2026-06-20 16:53:29 -04:00
parent 7c0357b4c5
commit c0d3a30176
17 changed files with 62 additions and 43 deletions
@@ -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