diff --git a/src/PowderCoating.Application/Interfaces/ISeedDataService.cs b/src/PowderCoating.Application/Interfaces/ISeedDataService.cs
index d60169a..38486ce 100644
--- a/src/PowderCoating.Application/Interfaces/ISeedDataService.cs
+++ b/src/PowderCoating.Application/Interfaces/ISeedDataService.cs
@@ -48,6 +48,16 @@ public class RemoveSeedDataOptions
public bool Bills { get; set; }
public bool Expenses { get; set; }
public bool Workers { get; set; }
+ public bool Vendors { get; set; }
+ public bool NamedOvens { get; set; }
+ public bool Appointments { get; set; }
+
+ ///
+ /// When true, all removal blocks skip fingerprint matching and delete by CompanyId only.
+ /// Use for demo resets where the goal is a full wipe regardless of which code version seeded
+ /// the data. Never set this on a real tenant company.
+ ///
+ public bool ForceRemoveAll { get; set; }
}
public class SeedDataResult
diff --git a/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs b/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs
index cf2c356..ca4f69a 100644
--- a/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs
+++ b/src/PowderCoating.Infrastructure/Services/SeedDataService.Remove.cs
@@ -123,11 +123,15 @@ public partial class SeedDataService
// --- Customers (+ their jobs, quotes, and related items) ---
if (options.Customers)
{
- var seededCustomerIds = await _context.Customers
- .IgnoreQueryFilters()
- .Where(c => c.CompanyId == companyId && SeededCustomerEmails.Contains(c.Email))
- .Select(c => c.Id)
- .ToListAsync();
+ // ForceRemoveAll: wipe every customer for the company (demo reset path).
+ // Normal mode: fingerprint-match by seeded email addresses (selective removal).
+ var seededCustomerIds = options.ForceRemoveAll
+ ? await _context.Customers.IgnoreQueryFilters()
+ .Where(c => c.CompanyId == companyId)
+ .Select(c => c.Id).ToListAsync()
+ : await _context.Customers.IgnoreQueryFilters()
+ .Where(c => c.CompanyId == companyId && SeededCustomerEmails.Contains(c.Email))
+ .Select(c => c.Id).ToListAsync();
if (seededCustomerIds.Any())
{
@@ -452,12 +456,81 @@ public partial class SeedDataService
}
}
+ // --- Vendors ---
+ if (options.Vendors || options.ForceRemoveAll)
+ {
+ var vendors = await _context.Set()
+ .IgnoreQueryFilters()
+ .Where(v => v.CompanyId == companyId)
+ .ToListAsync();
+
+ if (vendors.Any())
+ {
+ _context.Set().RemoveRange(vendors);
+ totalRemoved += vendors.Count;
+ details.Add($"✓ Removed {vendors.Count} vendor(s)");
+ await _context.SaveChangesAsync();
+ }
+ else
+ {
+ details.Add("• No vendors found");
+ }
+ }
+
+ // --- Named Ovens (OvenCost) ---
+ if (options.NamedOvens || options.ForceRemoveAll)
+ {
+ var ovens = await _context.Set()
+ .IgnoreQueryFilters()
+ .Where(o => o.CompanyId == companyId)
+ .ToListAsync();
+
+ if (ovens.Any())
+ {
+ _context.Set().RemoveRange(ovens);
+ totalRemoved += ovens.Count;
+ details.Add($"✓ Removed {ovens.Count} named oven(s)");
+ await _context.SaveChangesAsync();
+ }
+ else
+ {
+ details.Add("• No named ovens found");
+ }
+ }
+
+ // --- Appointments ---
+ if (options.Appointments || options.ForceRemoveAll)
+ {
+ var appointments = await _context.Set()
+ .IgnoreQueryFilters()
+ .Where(a => a.CompanyId == companyId)
+ .ToListAsync();
+
+ if (appointments.Any())
+ {
+ _context.Set().RemoveRange(appointments);
+ totalRemoved += appointments.Count;
+ details.Add($"✓ Removed {appointments.Count} appointment(s)");
+ await _context.SaveChangesAsync();
+ }
+ else
+ {
+ details.Add("• No appointments found");
+ }
+ }
+
// --- Shop Workers ---
if (options.Workers)
{
- var workerUsers = await _userManager.Users
- .Where(u => SeededWorkerEmails.Contains(u.Email) && u.CompanyId == companyId)
- .ToListAsync();
+ // ForceRemoveAll: remove all non-admin company users (role = Worker/Employee).
+ // Normal mode: fingerprint-match by @pcldemo.com email domain.
+ var workerUsers = options.ForceRemoveAll
+ ? await _userManager.Users
+ .Where(u => u.CompanyId == companyId && u.CompanyRole == "Worker")
+ .ToListAsync()
+ : await _userManager.Users
+ .Where(u => SeededWorkerEmails.Contains(u.Email) && u.CompanyId == companyId)
+ .ToListAsync();
if (workerUsers.Any())
{
diff --git a/src/PowderCoating.Web/Controllers/SeedDataController.cs b/src/PowderCoating.Web/Controllers/SeedDataController.cs
index 0e88a5e..e178e21 100644
--- a/src/PowderCoating.Web/Controllers/SeedDataController.cs
+++ b/src/PowderCoating.Web/Controllers/SeedDataController.cs
@@ -125,7 +125,8 @@ public class SeedDataController : Controller
return RedirectToAction(nameof(Index));
}
- // Remove all seed data categories
+ // Full wipe — ForceRemoveAll bypasses fingerprint matching so stale seed data from
+ // previous code versions (different emails, renamed SKUs, etc.) is always cleared.
var removeOptions = new RemoveSeedDataOptions
{
Customers = true,
@@ -137,6 +138,10 @@ public class SeedDataController : Controller
Bills = true,
Expenses = true,
Workers = true,
+ Vendors = true,
+ NamedOvens = true,
+ Appointments = true,
+ ForceRemoveAll = true,
};
var removeResult = await _seedDataService.RemoveSeedDataAsync(demo.Id, removeOptions);