Add one-click Demo Company reset for tutorial recording prep

New ResetDemoCompany POST action wipes all seeded data (customers, jobs,
quotes, invoices, inventory, equipment, catalog, pricing tiers, operating
costs) from the DEMO company and immediately re-seeds with fresh records
dated relative to today. Seed data already used relative dates so every
reset produces a realistic, current-looking dataset.

View adds a red "Reset Demo Company" card at the top of the Seed Data page,
visible only when the DEMO company exists. Single button with confirm dialog;
shows exactly what will be wiped and what will be preserved (user accounts,
company settings, lookup tables).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 21:34:57 -04:00
parent 35264e6b2a
commit 86a293a927
2 changed files with 107 additions and 0 deletions
@@ -106,6 +106,75 @@ public class SeedDataController : Controller
return RedirectToAction(nameof(Index));
}
/// <summary>
/// Wipes all seeded data from the DEMO company and immediately re-seeds it with fresh demo data
/// so all dates are current. Intended for tutorial recording resets — one click returns the demo
/// company to a clean, realistic state without touching any other tenant.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ResetDemoCompany()
{
try
{
var companies = await _seedDataService.GetCompaniesAsync();
var demo = companies.FirstOrDefault(c => c.CompanyCode == "DEMO");
if (demo == null)
{
TempData["ErrorMessage"] = "Demo company (code: DEMO) not found. Run Seed System Data first.";
return RedirectToAction(nameof(Index));
}
// Remove all seed data categories
var removeOptions = new RemoveSeedDataOptions
{
Customers = true,
InventoryItems = true,
Equipment = true,
Catalog = true,
PricingTiers = true,
OperatingCosts = true,
};
var removeResult = await _seedDataService.RemoveSeedDataAsync(demo.Id, removeOptions);
if (!removeResult.Success)
{
TempData["ErrorMessage"] = $"Wipe step failed: {removeResult.Message}";
return RedirectToAction(nameof(Index));
}
// Re-seed with today's dates
var seedResult = await _seedDataService.SeedCompanyDataAsync(demo.Id);
if (seedResult.Success)
{
TempData["SuccessMessage"] = $"Demo company reset complete. {seedResult.ItemsSeeded} records re-seeded with today&apos;s dates.";
TempData["SeedDetails"] = string.Join("|", seedResult.Details);
TempData["ItemsSeeded"] = seedResult.ItemsSeeded;
if (seedResult.Warnings.Any())
{
TempData["WarningMessage"] = $"{seedResult.ItemsSkipped} item(s) were skipped";
var displayWarnings = seedResult.Warnings.Take(30).ToList();
if (seedResult.Warnings.Count > 30)
displayWarnings.Add($"&hellip; and {seedResult.Warnings.Count - 30} more (see logs)");
TempData["SeedWarnings"] = string.Join("|", displayWarnings);
}
}
else
{
TempData["ErrorMessage"] = $"Wipe succeeded but re-seed failed: {seedResult.Message}";
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error resetting demo company");
TempData["ErrorMessage"] = $"An error occurred during demo reset: {ex.Message}";
}
return RedirectToAction(nameof(Index));
}
/// <summary>
/// Removes previously seeded demo data from a company according to the supplied options (e.g., jobs only, or all data). Used during QA/demo resets to return a company to a clean state without a full database drop.
/// </summary>
@@ -98,6 +98,44 @@
</div>
}
<!-- Demo Reset Card — only shown when the DEMO company exists -->
@{
var demoCompany = Model?.FirstOrDefault(c => c.CompanyCode == "DEMO");
}
@if (demoCompany != null)
{
<div class="card mb-4 border-danger">
<div class="card-header bg-danger text-white d-flex align-items-center gap-2">
<i class="bi bi-arrow-repeat fs-5"></i>
<h5 class="mb-0">Reset Demo Company</h5>
<span class="badge bg-white text-danger ms-auto">Tutorial Prep</span>
</div>
<div class="card-body">
<p class="card-text">
Wipes <strong>all seeded data</strong> from the Demo Company and immediately re-seeds it with
fresh records dated relative to <strong>today</strong>. Use this before every recording session
so jobs, quotes, invoices, and AR aging always look current.
</p>
<ul class="mb-3 small">
<li>Removes: customers, jobs, quotes, invoices, inventory, equipment, catalog, pricing tiers, operating costs</li>
<li>Re-seeds: 100 customers, 50 jobs across all statuses, quotes, invoices, inventory transactions, vendor bills, appointments &mdash; all dated from today</li>
<li>Preserves: user accounts, company settings, lookup tables (job statuses, priorities, etc.)</li>
</ul>
<div class="alert alert-warning alert-permanent mb-3">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<strong>This permanently deletes and recreates all demo data.</strong> Any manual edits made to the demo company will be lost.
</div>
<form asp-action="ResetDemoCompany" method="post"
onsubmit="return confirm('Reset the Demo Company?\n\nThis will DELETE all seeded data and re-seed it with fresh records dated today.\n\nAny manual edits to the demo company will be lost.');">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-danger">
<i class="bi bi-arrow-repeat me-2"></i>Reset Demo Company Now
</button>
</form>
</div>
</div>
}
<!-- System Data Card -->
<div class="card mb-4 border-primary">
<div class="card-header bg-primary text-white">