Demo reset + dev banner suppression for DEMO company
- DemoController: company-code-gated reset action (DEMO only, CSRF protected) - SeedDataService.Remove: FK-safe topological pre-sweep, all deletes scoped to companyId - SeedDataService: clock entries, extra seed data, updated customer/worker/job-status seeders - CompanySettingsController + Index.cshtml: Reset Demo Data button for DEMO company users - ReportsController + FinancialReportService: supporting report fixes - _Layout.cshtml: suppress env banner when current company is DEMO (all auth paths) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,32 +7,26 @@ namespace PowderCoating.Infrastructure.Services;
|
||||
public partial class SeedDataService
|
||||
{
|
||||
/// <summary>
|
||||
/// Canonical emails of the 5 demo shop workers. Used as fingerprints in RemoveSeedDataAsync
|
||||
/// to avoid needing a special "IsSeeded" flag on ApplicationUser.
|
||||
/// Canonical emails of the 3 demo employees (2 workers + 1 manager). Used as fingerprints
|
||||
/// in RemoveSeedDataAsync to avoid needing a special "IsSeeded" flag on ApplicationUser.
|
||||
/// </summary>
|
||||
internal static readonly string[] SeededWorkerEmails =
|
||||
[
|
||||
"mike.sanders@pcldemo.com",
|
||||
"jake.wilson@pcldemo.com",
|
||||
"sarah.brooks@pcldemo.com",
|
||||
"tyler.green@pcldemo.com",
|
||||
"chris.mason@pcldemo.com",
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Seeds 5 named shop workers as ApplicationUser records for the demo company:
|
||||
/// Mike Sanders (Coater), Jake Wilson (Sandblaster), Sarah Brooks (Inspector),
|
||||
/// Tyler Green (General Worker), and Chris Mason (Shop Lead).
|
||||
/// Seeds 3 named employees as ApplicationUser records for the demo company:
|
||||
/// Mike Sanders (Coater, Worker), Jake Wilson (Sandblaster, Worker),
|
||||
/// and Sarah Brooks (Shop Manager, Manager role with broader permissions).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Workers are ApplicationUser records with CompanyRole = ShopFloor and the
|
||||
/// Employee system role. They are seeded before jobs and time entries so that
|
||||
/// AssignedUserId on Job and UserId on JobTimeEntry can reference them.
|
||||
///
|
||||
/// Uses a consistent email domain (@pcldemo.com) that will never conflict with
|
||||
/// real user accounts, making them safe to identify and remove on Demo Reset.
|
||||
///
|
||||
/// Idempotency: bails early if any of the 5 worker emails already exist.
|
||||
/// Workers are seeded before jobs and time entries so that AssignedUserId on Job
|
||||
/// and UserId on JobTimeEntry and EmployeeClockEntry can reference them.
|
||||
/// Uses @pcldemo.com email domain — will never conflict with real accounts.
|
||||
/// Idempotency: bails early if any of the 3 emails already exist for this company.
|
||||
/// </remarks>
|
||||
private async Task<int> SeedShopWorkersAsync(Company company)
|
||||
{
|
||||
@@ -40,50 +34,71 @@ public partial class SeedDataService
|
||||
.AnyAsync(u => SeededWorkerEmails.Contains(u.Email) && u.CompanyId == company.Id);
|
||||
if (anyExists) return 0;
|
||||
|
||||
const string defaultPassword = "Worker123!";
|
||||
const string pwd = "Worker123!Demo";
|
||||
var hireDate = DateTime.UtcNow.AddMonths(-18);
|
||||
int created = 0;
|
||||
|
||||
// (email, firstName, lastName, empNum, position, laborRate)
|
||||
var workers = new (string email, string fn, string ln, string emp, string pos, decimal rate)[]
|
||||
// ── 2 shop workers ────────────────────────────────────────────────────────
|
||||
foreach (var (email, fn, ln, emp, pos, rate) in new (string, string, string, string, string, decimal)[]
|
||||
{
|
||||
("mike.sanders@pcldemo.com", "Mike", "Sanders", "EMP-001", "Coater", 22.00m),
|
||||
("jake.wilson@pcldemo.com", "Jake", "Wilson", "EMP-002", "Sandblaster", 20.00m),
|
||||
("sarah.brooks@pcldemo.com", "Sarah", "Brooks", "EMP-003", "Quality Inspector", 24.00m),
|
||||
("tyler.green@pcldemo.com", "Tyler", "Green", "EMP-004", "General Worker", 18.00m),
|
||||
("chris.mason@pcldemo.com", "Chris", "Mason", "EMP-005", "Shop Lead", 28.00m),
|
||||
};
|
||||
|
||||
foreach (var (email, fn, ln, emp, pos, rate) in workers)
|
||||
("mike.sanders@pcldemo.com", "Mike", "Sanders", "EMP-001", "Coater", 22.00m),
|
||||
("jake.wilson@pcldemo.com", "Jake", "Wilson", "EMP-002", "Sandblaster", 20.00m),
|
||||
})
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(email);
|
||||
if (user != null) continue;
|
||||
|
||||
user = new ApplicationUser
|
||||
if (await _userManager.FindByEmailAsync(email) != null) continue;
|
||||
var user = new ApplicationUser
|
||||
{
|
||||
UserName = email,
|
||||
Email = email,
|
||||
FirstName = fn,
|
||||
LastName = ln,
|
||||
EmployeeNumber = emp,
|
||||
UserName = email, Email = email,
|
||||
FirstName = fn, LastName = ln,
|
||||
EmployeeNumber = emp, Position = pos,
|
||||
Department = "Shop Floor",
|
||||
Position = pos,
|
||||
LaborCostPerHour = rate,
|
||||
EmailConfirmed = true,
|
||||
HireDate = DateTime.UtcNow.AddMonths(-12),
|
||||
IsActive = true,
|
||||
EmailConfirmed = true, IsActive = true,
|
||||
HireDate = hireDate,
|
||||
CompanyId = company.Id,
|
||||
CompanyRole = AppConstants.CompanyRoles.Worker,
|
||||
CanManageJobs = true,
|
||||
CanViewShopFloor = true,
|
||||
CreatedAt = DateTime.UtcNow.AddMonths(-12)
|
||||
CreatedAt = hireDate
|
||||
};
|
||||
var r = await _userManager.CreateAsync(user, pwd);
|
||||
if (!r.Succeeded)
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to create {email}: {string.Join("; ", r.Errors.Select(e => e.Description))}");
|
||||
await _userManager.AddToRoleAsync(user, AppConstants.Roles.Employee);
|
||||
created++;
|
||||
}
|
||||
|
||||
var result = await _userManager.CreateAsync(user, defaultPassword);
|
||||
if (result.Succeeded)
|
||||
// ── 1 manager ─────────────────────────────────────────────────────────────
|
||||
const string managerEmail = "sarah.brooks@pcldemo.com";
|
||||
if (await _userManager.FindByEmailAsync(managerEmail) == null)
|
||||
{
|
||||
var mgr = new ApplicationUser
|
||||
{
|
||||
await _userManager.AddToRoleAsync(user, AppConstants.Roles.Employee);
|
||||
created++;
|
||||
}
|
||||
UserName = managerEmail, Email = managerEmail,
|
||||
FirstName = "Sarah", LastName = "Brooks",
|
||||
EmployeeNumber = "EMP-003", Position = "Shop Manager",
|
||||
Department = "Management",
|
||||
LaborCostPerHour = 32.00m,
|
||||
EmailConfirmed = true, IsActive = true,
|
||||
HireDate = hireDate.AddMonths(-6), // hired before the workers
|
||||
CompanyId = company.Id,
|
||||
CompanyRole = AppConstants.CompanyRoles.Manager,
|
||||
CanManageJobs = true, CanViewShopFloor = true,
|
||||
CanManageCustomers = true, CanCreateQuotes = true,
|
||||
CanApproveQuotes = true, CanManageCalendar = true,
|
||||
CanViewCalendar = true, CanManageProducts = true,
|
||||
CanViewProducts = true, CanManageEquipment = true,
|
||||
CanManageMaintenance = true, CanManageInventory = true,
|
||||
CanViewReports = true,
|
||||
CreatedAt = hireDate.AddMonths(-6)
|
||||
};
|
||||
var r = await _userManager.CreateAsync(mgr, pwd);
|
||||
if (!r.Succeeded)
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to create {managerEmail}: {string.Join("; ", r.Errors.Select(e => e.Description))}");
|
||||
await _userManager.AddToRoleAsync(mgr, AppConstants.Roles.Employee);
|
||||
created++;
|
||||
}
|
||||
|
||||
return created;
|
||||
|
||||
Reference in New Issue
Block a user