Files
PowderCoatingLogix/src/PowderCoating.Web/Views/SeedData/Index.cshtml
T
spouliot 86a293a927 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>
2026-06-10 21:34:57 -04:00

450 lines
24 KiB
Plaintext

@model List<PowderCoating.Core.Entities.Company>
@{
ViewData["Title"] = "Seed Data Management";
ViewData["PageIcon"] = "bi-database-fill-gear";
}
@section Styles {
<style>
/* Dark mode: modal header with bg-warning */
[data-bs-theme="dark"] .modal-header.bg-warning {
background-color: #664d03 !important;
color: #fff !important;
}
[data-bs-theme="dark"] .modal-header.bg-warning .btn-close {
filter: invert(1);
}
</style>
}
<div class="container-fluid">
<div class="mb-2">
<a asp-controller="PlatformAdmin" asp-action="Maintenance" class="text-muted small text-decoration-none">
<i class="bi bi-arrow-left me-1"></i>Maintenance
</a>
</div>
<environment include="Production">
<div class="alert alert-danger alert-permanent d-flex gap-3 align-items-start mb-4">
<i class="bi bi-exclamation-octagon-fill fs-4 flex-shrink-0 mt-1 text-danger"></i>
<div>
<div class="fw-semibold">You are running in Production</div>
<div class="small mt-1">Seed operations write data directly to the live database. Only proceed if you have a specific, intentional reason &mdash; for example, onboarding a new company. Do not seed the default demo company in production.</div>
</div>
</div>
</environment>
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success alert-dismissible alert-permanent fade show" role="alert">
<i class="bi bi-check-circle-fill me-2"></i>
<strong>Success!</strong> @TempData["SuccessMessage"]
@if (TempData["SeedDetails"] != null)
{
var details = TempData["SeedDetails"].ToString()?.Split('|');
if (details != null && details.Length > 0)
{
<div class="mt-2">
@foreach (var detail in details)
{
<div class="small">@detail</div>
}
</div>
}
}
@if (TempData["ItemsSeeded"] != null)
{
<div class="mt-2">
<span class="badge bg-success">@TempData["ItemsSeeded"] items seeded</span>
</div>
}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
@if (TempData["WarningMessage"] != null)
{
<div class="alert alert-warning alert-dismissible alert-permanent fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<strong>Warning!</strong> @TempData["WarningMessage"]
@if (TempData["SeedWarnings"] != null)
{
var warnings = TempData["SeedWarnings"].ToString()?.Split('|');
if (warnings != null && warnings.Length > 0)
{
<div class="mt-2">
<details>
<summary class="small text-decoration-underline" style="cursor: pointer;">View skipped items</summary>
<div class="mt-2">
@foreach (var warning in warnings)
{
<div class="small">@warning</div>
}
</div>
</details>
</div>
}
}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
@if (TempData["ErrorMessage"] != null)
{
<div class="alert alert-danger alert-dismissible alert-permanent fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<strong>Error!</strong> @TempData["ErrorMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</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">
<h5 class="mb-0"><i class="bi bi-gear-fill me-2"></i>System Data</h5>
</div>
<div class="card-body">
<p class="card-text">
Seed system-level data including:
</p>
<ul class="mb-3">
<li><strong>Roles:</strong> SuperAdmin, Administrator, Manager, Employee, ShopFloor, ReadOnly</li>
<li><strong>Default Company:</strong> Demo Company (DEMO)</li>
<li><strong>SuperAdmin User:</strong> artemis@("@")powdercoatinglogix.com</li>
</ul>
<div class="alert alert-info alert-permanent">
<i class="bi bi-info-circle-fill me-2"></i>
<strong>Note:</strong> This operation is idempotent. If data already exists, it will be skipped.
</div>
<form asp-action="SeedSystem" method="post" onsubmit="return confirm('Are you sure you want to seed system data? This will create default roles, companies, and SuperAdmin users.');">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-primary">
<i class="bi bi-database-fill-add me-2"></i>Seed System Data
</button>
</form>
</div>
</div>
<!-- Company Data Card -->
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0"><i class="bi bi-building me-2"></i>Company Data</h5>
</div>
<div class="card-body">
<p class="card-text">
Seed company-specific data including:
</p>
<ul class="mb-3">
<li><strong>Inventory Items:</strong> 8 powder coating colors + 2 consumables</li>
<li><strong>Operating Costs:</strong> Default labor, equipment, and overhead rates</li>
<li><strong>Demo Users:</strong> Company Admin and Manager (only for Demo Company)</li>
</ul>
<div class="alert alert-info alert-permanent mb-3">
<i class="bi bi-info-circle-fill me-2"></i>
<strong>Note:</strong> Select a company below to seed demo data. Existing data will not be overwritten.
</div>
@if (Model == null || !Model.Any())
{
<div class="alert alert-warning alert-permanent">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
No companies found. Please seed system data first to create the default company.
</div>
}
else
{
<!-- Desktop table -->
<div class="table-responsive d-none d-lg-block">
<table class="table table-hover">
<thead>
<tr>
<th>Company Name</th>
<th>Code</th>
<th>Status</th>
<th>Subscription Plan</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
@foreach (var company in Model)
{
<tr>
<td>
<strong>@company.CompanyName</strong>
@if (company.CompanyCode == "DEMO")
{
<span class="badge bg-info ms-2">Default</span>
}
</td>
<td><code>@company.CompanyCode</code></td>
<td>
@if (company.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
}
</td>
<td>@company.SubscriptionPlan</td>
<td class="text-center">
<div class="d-flex gap-2 justify-content-center">
<form asp-action="SeedCompany" method="post" style="display: inline;"
onsubmit="return confirm('Are you sure you want to seed data for @company.CompanyName? Existing data will not be overwritten.');">
@Html.AntiForgeryToken()
<input type="hidden" name="companyId" value="@company.Id" />
<button type="submit" class="btn btn-success btn-sm">
<i class="bi bi-database-fill-add me-1"></i>Seed Data
</button>
</form>
<button type="button" class="btn btn-warning btn-sm"
onclick="openUnseedModal(@company.Id, '@Html.Raw(Html.Encode(company.CompanyName))')">
<i class="bi bi-database-fill-dash me-1"></i>Unseed Data
</button>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
<!-- Mobile card view &mdash; shown on screens < 992px -->
<div class="mobile-card-view d-lg-none">
<div class="mobile-card-list">
@foreach (var company in Model)
{
<div class="mobile-data-card">
<div class="mobile-card-header">
<div class="mobile-card-icon bg-success"><i class="bi bi-building"></i></div>
<div class="mobile-card-title">
<h6>
@company.CompanyName
@if (company.CompanyCode == "DEMO")
{
<span class="badge bg-info ms-1">Default</span>
}
</h6>
<small><code>@company.CompanyCode</code></small>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<span class="mobile-card-label">Status</span>
<span class="mobile-card-value">
@if (company.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
}
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Plan</span>
<span class="mobile-card-value">@company.SubscriptionPlan</span>
</div>
</div>
<div class="mobile-card-footer">
<form asp-action="SeedCompany" method="post" style="display: inline;"
onsubmit="return confirm('Are you sure you want to seed data for @company.CompanyName? Existing data will not be overwritten.');">
@Html.AntiForgeryToken()
<input type="hidden" name="companyId" value="@company.Id" />
<button type="submit" class="btn btn-success btn-sm me-1">
<i class="bi bi-database-fill-add me-1"></i>Seed
</button>
</form>
<button type="button" class="btn btn-warning btn-sm"
onclick="openUnseedModal(@company.Id, '@Html.Raw(Html.Encode(company.CompanyName))')">
<i class="bi bi-database-fill-dash me-1"></i>Unseed
</button>
</div>
</div>
}
</div>
</div>
}
</div>
</div>
<environment exclude="Production">
<!-- Documentation Card -->
<div class="card mt-4 border-info">
<div class="card-header bg-info text-white">
<h5 class="mb-0"><i class="bi bi-book me-2"></i>Default Credentials</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6 class="fw-bold">SuperAdmin (Platform Access)</h6>
<ul class="list-unstyled">
<li class="mb-2">
<strong>SuperAdmin:</strong><br />
Email: <code>artemis@("@")powdercoatinglogix.com</code><br />
Password: <code>SuperAdmin123!</code>
</li>
</ul>
</div>
<div class="col-md-6">
<h6 class="fw-bold">Demo Company Users</h6>
<ul class="list-unstyled">
<li class="mb-2">
<strong>Company Admin:</strong><br />
Email: <code>demo@("@")powdercoatinglogix.com</code><br />
Password: <code>CompanyAdmin123!</code>
</li>
<li>
<strong>Manager:</strong><br />
Email: <code>manager@("@")demo.com</code><br />
Password: <code>Manager123!</code>
</li>
</ul>
</div>
</div>
</div>
</div>
</environment>
</div>
<!-- Unseed Data Modal -->
<div class="modal fade" id="unseedModal" tabindex="-1" aria-labelledby="unseedModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="unseedModalLabel">
<i class="bi bi-database-fill-dash me-2"></i>Unseed Data
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form asp-action="RemoveSeedData" method="post" id="unseedForm">
@Html.AntiForgeryToken()
<input type="hidden" name="companyId" id="unseedCompanyId" />
<div class="modal-body">
<p class="mb-1">Select the data types to remove for <strong id="unseedCompanyName"></strong>.</p>
<p class="text-muted small mb-3">Only records that match the original seed values will be removed. Any data you have added or modified will not be affected.</p>
<div class="list-group">
<label class="list-group-item list-group-item-action d-flex gap-3">
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.Customers" value="true" id="chkCustomers" />
<span>
<strong>Customers</strong>
<span class="d-block text-muted small">Also removes associated jobs, quotes, and their line items (25 seeded customers)</span>
</span>
</label>
<label class="list-group-item list-group-item-action d-flex gap-3">
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.InventoryItems" value="true" id="chkInventory" />
<span>
<strong>Inventory Items</strong>
<span class="d-block text-muted small">10 seeded items (8 powder colors, cleaner, masking tape) and their transactions</span>
</span>
</label>
<label class="list-group-item list-group-item-action d-flex gap-3">
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.Equipment" value="true" id="chkEquipment" />
<span>
<strong>Equipment</strong>
<span class="d-block text-muted small">10 seeded equipment records and their maintenance history</span>
</span>
</label>
<label class="list-group-item list-group-item-action d-flex gap-3">
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.Catalog" value="true" id="chkCatalog" />
<span>
<strong>Catalog Items &amp; Categories</strong>
<span class="d-block text-muted small">7 categories and all their catalog items</span>
</span>
</label>
<label class="list-group-item list-group-item-action d-flex gap-3">
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.PricingTiers" value="true" id="chkPricingTiers" />
<span>
<strong>Pricing Tiers</strong>
<span class="d-block text-muted small">Standard, Silver, Gold, Platinum tiers</span>
</span>
</label>
<label class="list-group-item list-group-item-action d-flex gap-3">
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.OperatingCosts" value="true" id="chkOperatingCosts" />
<span>
<strong>Operating Costs</strong>
<span class="d-block text-muted small">Default labor, equipment, and overhead rates</span>
</span>
</label>
</div>
<div class="alert alert-warning alert-permanent mt-3 mb-0">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<strong>This action permanently deletes records and cannot be undone.</strong>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-warning" id="unseedSubmitBtn" disabled>
<i class="bi bi-database-fill-dash me-1"></i>Remove Selected Data
</button>
</div>
</form>
</div>
</div>
</div>
@section Scripts {
<script>
function openUnseedModal(companyId, companyName) {
document.getElementById('unseedCompanyId').value = companyId;
document.getElementById('unseedCompanyName').textContent = companyName;
// Reset checkboxes
document.querySelectorAll('#unseedForm input[type="checkbox"]').forEach(cb => cb.checked = false);
document.getElementById('unseedSubmitBtn').disabled = true;
new bootstrap.Modal(document.getElementById('unseedModal')).show();
}
// Enable submit only when at least one checkbox is checked
document.querySelectorAll('#unseedForm input[type="checkbox"]').forEach(cb => {
cb.addEventListener('change', function () {
const anyChecked = [...document.querySelectorAll('#unseedForm input[type="checkbox"]')].some(c => c.checked);
document.getElementById('unseedSubmitBtn').disabled = !anyChecked;
});
});
</script>
}