Fix Reset Demo Company: full wipe mode + missing removal categories
Root cause: fingerprint-based removal failed on databases seeded with older code (different emails/SKUs); plus Vendors, Named Ovens, and Appointments had no removal path at all. - Add ForceRemoveAll flag to RemoveSeedDataOptions: when true, all removal blocks delete by CompanyId instead of fingerprint matching - Customers block: ForceRemoveAll deletes all company customers - Workers block: ForceRemoveAll deletes all users with CompanyRole=Worker - New Vendors block (triggered by options.Vendors || ForceRemoveAll) - New NamedOvens (OvenCost) block (triggered by options.NamedOvens || ForceRemoveAll) - New Appointments block (triggered by options.Appointments || ForceRemoveAll) - ResetDemoCompany: set ForceRemoveAll=true and enable all new flags so every re-seedable table is wiped clean before re-seeding Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,16 @@ public class RemoveSeedDataOptions
|
|||||||
public bool Bills { get; set; }
|
public bool Bills { get; set; }
|
||||||
public bool Expenses { get; set; }
|
public bool Expenses { get; set; }
|
||||||
public bool Workers { get; set; }
|
public bool Workers { get; set; }
|
||||||
|
public bool Vendors { get; set; }
|
||||||
|
public bool NamedOvens { get; set; }
|
||||||
|
public bool Appointments { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public bool ForceRemoveAll { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeedDataResult
|
public class SeedDataResult
|
||||||
|
|||||||
@@ -123,11 +123,15 @@ public partial class SeedDataService
|
|||||||
// --- Customers (+ their jobs, quotes, and related items) ---
|
// --- Customers (+ their jobs, quotes, and related items) ---
|
||||||
if (options.Customers)
|
if (options.Customers)
|
||||||
{
|
{
|
||||||
var seededCustomerIds = await _context.Customers
|
// ForceRemoveAll: wipe every customer for the company (demo reset path).
|
||||||
.IgnoreQueryFilters()
|
// Normal mode: fingerprint-match by seeded email addresses (selective removal).
|
||||||
.Where(c => c.CompanyId == companyId && SeededCustomerEmails.Contains(c.Email))
|
var seededCustomerIds = options.ForceRemoveAll
|
||||||
.Select(c => c.Id)
|
? await _context.Customers.IgnoreQueryFilters()
|
||||||
.ToListAsync();
|
.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())
|
if (seededCustomerIds.Any())
|
||||||
{
|
{
|
||||||
@@ -452,12 +456,81 @@ public partial class SeedDataService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Vendors ---
|
||||||
|
if (options.Vendors || options.ForceRemoveAll)
|
||||||
|
{
|
||||||
|
var vendors = await _context.Set<Core.Entities.Vendor>()
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.Where(v => v.CompanyId == companyId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (vendors.Any())
|
||||||
|
{
|
||||||
|
_context.Set<Core.Entities.Vendor>().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<Core.Entities.OvenCost>()
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.Where(o => o.CompanyId == companyId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (ovens.Any())
|
||||||
|
{
|
||||||
|
_context.Set<Core.Entities.OvenCost>().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<Core.Entities.Appointment>()
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.Where(a => a.CompanyId == companyId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (appointments.Any())
|
||||||
|
{
|
||||||
|
_context.Set<Core.Entities.Appointment>().RemoveRange(appointments);
|
||||||
|
totalRemoved += appointments.Count;
|
||||||
|
details.Add($"✓ Removed {appointments.Count} appointment(s)");
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
details.Add("• No appointments found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Shop Workers ---
|
// --- Shop Workers ---
|
||||||
if (options.Workers)
|
if (options.Workers)
|
||||||
{
|
{
|
||||||
var workerUsers = await _userManager.Users
|
// ForceRemoveAll: remove all non-admin company users (role = Worker/Employee).
|
||||||
.Where(u => SeededWorkerEmails.Contains(u.Email) && u.CompanyId == companyId)
|
// Normal mode: fingerprint-match by @pcldemo.com email domain.
|
||||||
.ToListAsync();
|
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())
|
if (workerUsers.Any())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ public class SeedDataController : Controller
|
|||||||
return RedirectToAction(nameof(Index));
|
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
|
var removeOptions = new RemoveSeedDataOptions
|
||||||
{
|
{
|
||||||
Customers = true,
|
Customers = true,
|
||||||
@@ -137,6 +138,10 @@ public class SeedDataController : Controller
|
|||||||
Bills = true,
|
Bills = true,
|
||||||
Expenses = true,
|
Expenses = true,
|
||||||
Workers = true,
|
Workers = true,
|
||||||
|
Vendors = true,
|
||||||
|
NamedOvens = true,
|
||||||
|
Appointments = true,
|
||||||
|
ForceRemoveAll = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var removeResult = await _seedDataService.RemoveSeedDataAsync(demo.Id, removeOptions);
|
var removeResult = await _seedDataService.RemoveSeedDataAsync(demo.Id, removeOptions);
|
||||||
|
|||||||
Reference in New Issue
Block a user