Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
-- Check customer 2266 details
SELECT
Id,
CompanyName,
ContactFirstName,
ContactLastName,
Email,
Phone,
IsCommercial,
IsDeleted,
CreatedAt
FROM Customers
WHERE Id = 2266;
+59
View File
@@ -0,0 +1,59 @@
-- Quick Database State Check for Lookup Tables
-- =====================================================
PRINT '=== LOOKUP TABLES STATUS ===';
PRINT '';
-- Check if lookup tables exist
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'JobStatusLookups')
PRINT '✓ JobStatusLookups table exists'
ELSE
PRINT '✗ JobStatusLookups table MISSING';
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'JobPriorityLookups')
PRINT '✓ JobPriorityLookups table exists'
ELSE
PRINT '✗ JobPriorityLookups table MISSING';
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'QuoteStatusLookups')
PRINT '✓ QuoteStatusLookups table exists'
ELSE
PRINT '✗ QuoteStatusLookups table MISSING';
PRINT '';
PRINT '=== DATA COUNTS ===';
PRINT '';
-- Count lookup records per company
SELECT
c.Name AS CompanyName,
(SELECT COUNT(*) FROM JobStatusLookups WHERE CompanyId = c.Id) AS JobStatuses,
(SELECT COUNT(*) FROM JobPriorityLookups WHERE CompanyId = c.Id) AS JobPriorities,
(SELECT COUNT(*) FROM QuoteStatusLookups WHERE CompanyId = c.Id) AS QuoteStatuses,
(SELECT COUNT(*) FROM Jobs WHERE CompanyId = c.Id) AS Jobs,
(SELECT COUNT(*) FROM Quotes WHERE CompanyId = c.Id) AS Quotes
FROM Companies c
WHERE c.IsDeleted = 0;
PRINT '';
PRINT '=== JOBS WITH/WITHOUT LOOKUP IDS ===';
PRINT '';
-- Check if Jobs have lookup IDs
SELECT
'Jobs with NULL JobStatusId' AS Issue,
COUNT(*) AS Count
FROM Jobs
WHERE JobStatusId IS NULL
UNION ALL
SELECT
'Jobs with NULL JobPriorityId' AS Issue,
COUNT(*) AS Count
FROM Jobs
WHERE JobPriorityId IS NULL
UNION ALL
SELECT
'Quotes with NULL QuoteStatusId' AS Issue,
COUNT(*) AS Count
FROM Quotes
WHERE QuoteStatusId IS NULL;
+30
View File
@@ -0,0 +1,30 @@
-- Check quote QT-2602-0050 and its customer
DECLARE @QuoteNumber NVARCHAR(50) = 'QT-2602-0050';
-- Find the quote and customer info
SELECT
q.Id AS QuoteId,
q.QuoteNumber,
q.CustomerId,
CASE WHEN q.CustomerId IS NULL THEN 1 ELSE 0 END AS IsProspect,
q.ProspectCompanyName,
q.ProspectContactName,
q.IsDeleted AS QuoteIsDeleted,
c.Id AS CustomerIdFromTable,
c.CompanyName AS CustomerCompanyName,
c.IsDeleted AS CustomerIsDeleted
FROM Quotes q
LEFT JOIN Customers c ON q.CustomerId = c.Id
WHERE q.QuoteNumber = @QuoteNumber;
-- Show all prospect and customer fields
SELECT
QuoteNumber,
CustomerId,
CASE WHEN CustomerId IS NULL THEN 'YES' ELSE 'NO' END AS IsProspect,
ProspectCompanyName,
ProspectContactName,
ProspectEmail,
ProspectPhone
FROM Quotes
WHERE QuoteNumber = @QuoteNumber;
Binary file not shown.
+47
View File
@@ -0,0 +1,47 @@
-- ============================================================
-- Delete All Users from a Company
-- Usage: Set @CompanyId to the target company's ID
-- WARNING: This is a hard delete. Data cannot be recovered.
-- ============================================================
DECLARE @CompanyId INT = 1 -- <-- Change this to the target company ID
BEGIN TRANSACTION;
BEGIN TRY
-- Get the user IDs to be deleted
DECLARE @UserIds TABLE (Id NVARCHAR(450));
INSERT INTO @UserIds
SELECT Id FROM AspNetUsers WHERE CompanyId = @CompanyId;
PRINT CONCAT('Users to be deleted: ', (SELECT COUNT(*) FROM @UserIds));
-- 1. Delete Identity-related child records first
DELETE FROM AspNetUserTokens WHERE UserId IN (SELECT Id FROM @UserIds);
DELETE FROM AspNetUserLogins WHERE UserId IN (SELECT Id FROM @UserIds);
DELETE FROM AspNetUserClaims WHERE UserId IN (SELECT Id FROM @UserIds);
DELETE FROM AspNetUserRoles WHERE UserId IN (SELECT Id FROM @UserIds);
-- 2. Nullify FK references in business tables (quotes, maintenance records)
UPDATE Quotes
SET PreparedById = NULL
WHERE PreparedById IN (SELECT Id FROM @UserIds);
UPDATE MaintenanceRecords
SET PerformedById = NULL
WHERE PerformedById IN (SELECT Id FROM @UserIds);
-- 3. Delete the users
DELETE FROM AspNetUsers WHERE Id IN (SELECT Id FROM @UserIds);
PRINT 'Users deleted successfully.';
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
PRINT CONCAT('Error: ', ERROR_MESSAGE());
THROW;
END CATCH;
+151
View File
@@ -0,0 +1,151 @@
# Fix-Passwords.ps1
# Resets password hashes for both SuperAdmin users in the Azure SQL database.
# Runs directly via sqlcmd to avoid copy-paste corruption of Base64 characters.
#
# Usage (interactive - will prompt for connection details):
# .\Fix-Passwords.ps1
#
# Usage (non-interactive):
# .\Fix-Passwords.ps1 -Server "yourserver.database.windows.net" -Database "PowderCoatingDb" -User "sqladmin" -Password "yourpassword"
param(
[string]$Server,
[string]$Database = "PowderCoatingapp",
[string]$User,
[string]$Password
)
function New-IdentityPasswordHash {
param([string]$Password)
$salt = [byte[]]::new(16)
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rng.GetBytes($salt)
$rng.Dispose()
$pbkdf2 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(
$Password, $salt, 100000,
[System.Security.Cryptography.HashAlgorithmName]::SHA256)
$subkey = $pbkdf2.GetBytes(32)
$pbkdf2.Dispose()
# ASP.NET Core Identity v3 format
$buf = [byte[]]::new(61)
$buf[0] = 0x01
$buf[1] = 0x00; $buf[2] = 0x00; $buf[3] = 0x00; $buf[4] = 0x01 # PRF = HMACSHA256
$buf[5] = 0x00; $buf[6] = 0x01; $buf[7] = 0x86; $buf[8] = 0xA0 # 100000 iterations
$buf[9] = 0x00; $buf[10] = 0x00; $buf[11] = 0x00; $buf[12] = 0x10 # salt length = 16
[Array]::Copy($salt, 0, $buf, 13, 16)
[Array]::Copy($subkey, 0, $buf, 29, 32)
return [Convert]::ToBase64String($buf)
}
# ---- Prompt for connection details if not provided ----------------------------
if (-not $Server) {
$Server = Read-Host "Azure SQL Server (e.g. yourserver.database.windows.net)"
}
if (-not $User) {
$User = Read-Host "SQL Username"
}
if (-not $Password) {
$securePass = Read-Host "SQL Password" -AsSecureString
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePass))
}
# ---- Check sqlcmd is available -----------------------------------------------
$sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue
if (-not $sqlcmd) {
Write-Host ""
Write-Host "ERROR: sqlcmd not found on PATH." -ForegroundColor Red
Write-Host "Install it from: https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility" -ForegroundColor Yellow
Write-Host ""
Write-Host "Alternatively, copy fix_passwords.sql to the Azure Portal Query Editor manually." -ForegroundColor Yellow
Write-Host "(Be careful that + and / characters are not changed during copy-paste.)" -ForegroundColor Yellow
$useSqlCmd = $false
} else {
$useSqlCmd = $true
}
# ---- Generate hashes ---------------------------------------------------------
Write-Host ""
Write-Host "Generating password hashes..." -ForegroundColor Cyan
$hash1 = New-IdentityPasswordHash "SuperAdmin123!"
$hash2 = New-IdentityPasswordHash "CompanyAdmin123!"
$stamp1 = [Guid]::NewGuid().ToString()
$stamp2 = [Guid]::NewGuid().ToString()
Write-Host "Done." -ForegroundColor Cyan
# ---- Build SQL ---------------------------------------------------------------
$sql = @"
SET QUOTED_IDENTIFIER ON;
SET NOCOUNT ON;
-- Reset password hash for superadmin@powdercoating.com / SuperAdmin123!
UPDATE AspNetUsers
SET
PasswordHash = '$hash1',
SecurityStamp = '$stamp1'
WHERE NormalizedEmail = 'SUPERADMIN@POWDERCOATING.COM';
PRINT 'Updated: superadmin@powdercoating.com';
-- Reset password hash for admin@demo.com / CompanyAdmin123!
UPDATE AspNetUsers
SET
PasswordHash = '$hash2',
SecurityStamp = '$stamp2'
WHERE NormalizedEmail = 'ADMIN@DEMO.COM';
PRINT 'Updated: admin@demo.com';
-- Verify both users exist and have non-null hashes
SELECT Email, LEN(PasswordHash) AS HashLength, IsActive
FROM AspNetUsers
WHERE NormalizedEmail IN ('SUPERADMIN@POWDERCOATING.COM', 'ADMIN@DEMO.COM');
"@
# ---- Write SQL file (always, as backup) --------------------------------------
$sqlFile = Join-Path $PSScriptRoot "fix_passwords.sql"
$sql | Set-Content -Path $sqlFile -Encoding UTF8
Write-Host "SQL written to: $sqlFile" -ForegroundColor Gray
# ---- Execute via sqlcmd (avoids copy-paste corruption) -----------------------
if ($useSqlCmd) {
Write-Host ""
Write-Host "Running via sqlcmd (no copy-paste, Base64 characters preserved)..." -ForegroundColor Cyan
$result = & sqlcmd `
-S $Server `
-d $Database `
-U $User `
-P $Password `
-Q $sql `
-l 30 2>&1
Write-Host ""
$result | ForEach-Object { Write-Host $_ }
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host "SUCCESS. Try logging in now:" -ForegroundColor Green
Write-Host " superadmin@powdercoating.com / SuperAdmin123!" -ForegroundColor White
Write-Host " admin@demo.com / CompanyAdmin123!" -ForegroundColor White
} else {
Write-Host ""
Write-Host "sqlcmd returned exit code $LASTEXITCODE. Check the output above." -ForegroundColor Red
Write-Host "You can also run fix_passwords.sql manually in the Azure Portal Query Editor." -ForegroundColor Yellow
}
} else {
Write-Host ""
Write-Host "Run fix_passwords.sql in Azure Portal > SQL Database > Query Editor." -ForegroundColor Yellow
Write-Host "TIP: If + or / appear as spaces after pasting, the hash will be corrupted again." -ForegroundColor Yellow
Write-Host " Use sqlcmd instead to be safe." -ForegroundColor Yellow
}
+32
View File
@@ -0,0 +1,32 @@
-- Fix Company Admin Permissions
-- This script updates all existing Company Admins to have all permissions
-- Update all users with CompanyRole = 'CompanyAdmin' to have all permissions
UPDATE AspNetUsers
SET
CanManageJobs = 1,
CanManageInventory = 1,
CanManageCustomers = 1,
CanCreateQuotes = 1,
CanApproveQuotes = 1,
CanManageCalendar = 1,
CanViewCalendar = 1,
CanManageProducts = 1,
CanViewProducts = 1,
CanManageEquipment = 1,
CanManageSuppliers = 1,
CanManageMaintenance = 1
WHERE CompanyRole = 'CompanyAdmin';
-- Show how many users were updated
SELECT
Id,
Email,
FirstName,
LastName,
CompanyRole,
CanManageCalendar,
CanManageProducts,
CanManageEquipment
FROM AspNetUsers
WHERE CompanyRole = 'CompanyAdmin';
+20
View File
@@ -0,0 +1,20 @@
-- Fix IsSystemDefined flag for Job Priorities
-- Only "NORMAL" should be system-defined, the rest should be editable
PRINT 'Fixing Job Priority system-defined flags...';
UPDATE JobPriorityLookups
SET IsSystemDefined = 0
WHERE PriorityCode IN ('LOW', 'HIGH', 'URGENT', 'RUSH');
PRINT 'Fixed. Only NORMAL priority is now system-defined.';
-- Verify
SELECT
PriorityCode,
DisplayName,
IsSystemDefined,
CASE WHEN IsSystemDefined = 1 THEN 'Protected' ELSE 'Editable' END AS Status
FROM JobPriorityLookups
WHERE IsDeleted = 0
ORDER BY DisplayOrder;
+264
View File
@@ -0,0 +1,264 @@
# Generate-SeedSql.ps1
# Generates seed_admins.sql with properly hashed passwords for ASP.NET Core Identity.
#
# Usage:
# .\Generate-SeedSql.ps1
#
# Requires: PowerShell 5.1+ on .NET Framework 4.7.2+ OR PowerShell 7+
function New-IdentityPasswordHash {
param([string]$Password)
# 16-byte random salt
$salt = [byte[]]::new(16)
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rng.GetBytes($salt)
$rng.Dispose()
# PBKDF2-HMAC-SHA256, 100,000 iterations, 32-byte output
$pbkdf2 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(
$Password, $salt, 100000,
[System.Security.Cryptography.HashAlgorithmName]::SHA256)
$subkey = $pbkdf2.GetBytes(32)
$pbkdf2.Dispose()
# ASP.NET Core Identity v3 wire format (61 bytes):
# [0] = 0x01 (version marker)
# [1-4] = 0x00000001 (PRF = HMACSHA256, big-endian uint32)
# [5-8] = 0x000186A0 (iterations = 100000, big-endian uint32)
# [9-12] = 0x00000010 (salt length = 16, big-endian uint32)
# [13-28] = salt bytes
# [29-60] = subkey bytes
$buf = [byte[]]::new(61)
$buf[0] = 0x01
$buf[1] = 0x00; $buf[2] = 0x00; $buf[3] = 0x00; $buf[4] = 0x01
$buf[5] = 0x00; $buf[6] = 0x01; $buf[7] = 0x86; $buf[8] = 0xA0
$buf[9] = 0x00; $buf[10] = 0x00; $buf[11] = 0x00; $buf[12] = 0x10
[Array]::Copy($salt, 0, $buf, 13, 16)
[Array]::Copy($subkey, 0, $buf, 29, 32)
return [Convert]::ToBase64String($buf)
}
# ---- Generate GUIDs and hashes -----------------------------------------------
$roleSuperAdmin = [Guid]::NewGuid().ToString()
$roleAdmin = [Guid]::NewGuid().ToString()
$roleManager = [Guid]::NewGuid().ToString()
$roleEmployee = [Guid]::NewGuid().ToString()
$roleShopFloor = [Guid]::NewGuid().ToString()
$roleReadOnly = [Guid]::NewGuid().ToString()
$user1Id = [Guid]::NewGuid().ToString()
$user2Id = [Guid]::NewGuid().ToString()
$user1Stamp = [Guid]::NewGuid().ToString()
$user2Stamp = [Guid]::NewGuid().ToString()
$user1Security = [Guid]::NewGuid().ToString()
$user2Security = [Guid]::NewGuid().ToString()
$now = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss")
Write-Host "Hashing SuperAdmin123! ..." -ForegroundColor Cyan
$hash1 = New-IdentityPasswordHash "SuperAdmin123!"
Write-Host "Hashing Admin123! ..." -ForegroundColor Cyan
$hash2 = New-IdentityPasswordHash "Admin123!"
Write-Host "Done. Writing SQL file..." -ForegroundColor Cyan
# ---- Build SQL ---------------------------------------------------------------
$outFile = Join-Path $PSScriptRoot "seed_admins.sql"
$lines = [System.Collections.Generic.List[string]]::new()
$lines.Add("-- =============================================================================")
$lines.Add("-- PowderCoating App - Initial Seed SQL")
$lines.Add("-- Generated: $now UTC")
$lines.Add("--")
$lines.Add("-- Run this against your Azure SQL database when startup seeding fails.")
$lines.Add("-- All inserts are guarded with IF NOT EXISTS and are safe to re-run.")
$lines.Add("-- =============================================================================")
$lines.Add("")
$lines.Add("SET NOCOUNT ON;")
$lines.Add("GO")
$lines.Add("")
$lines.Add("-- 1. Default Company")
$lines.Add("")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM Companies WHERE CompanyCode = 'DEMO')")
$lines.Add("BEGIN")
$lines.Add(" SET IDENTITY_INSERT Companies ON;")
$lines.Add("")
$lines.Add(" INSERT INTO Companies (")
$lines.Add(" Id, CompanyId, CompanyName, CompanyCode,")
$lines.Add(" PrimaryContactName, PrimaryContactEmail, Phone,")
$lines.Add(" Address, City, State, ZipCode,")
$lines.Add(" IsActive, SubscriptionPlan, SubscriptionStatus,")
$lines.Add(" SubscriptionStartDate, TimeZone,")
$lines.Add(" CreatedAt, IsDeleted")
$lines.Add(" ) VALUES (")
$lines.Add(" 1, 1, 'Demo Company', 'DEMO',")
$lines.Add(" 'Admin User', 'admin@demo.com', '(555) 123-4567',")
$lines.Add(" '123 Demo Street', 'Demo City', 'CA', '90210',")
$lines.Add(" 1, 2, 0,")
$lines.Add(" '$now', 'America/New_York',")
$lines.Add(" '$now', 0")
$lines.Add(" );")
$lines.Add("")
$lines.Add(" SET IDENTITY_INSERT Companies OFF;")
$lines.Add(" PRINT 'Company inserted.';")
$lines.Add("END")
$lines.Add("ELSE")
$lines.Add(" PRINT 'Company already exists - skipped.';")
$lines.Add("GO")
$lines.Add("")
$lines.Add("-- 2. Roles")
$lines.Add("")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'SUPERADMIN')")
$lines.Add(" INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)")
$lines.Add(" VALUES ('$roleSuperAdmin', 'SuperAdmin', 'SUPERADMIN', NEWID());")
$lines.Add("")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'ADMINISTRATOR')")
$lines.Add(" INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)")
$lines.Add(" VALUES ('$roleAdmin', 'Administrator', 'ADMINISTRATOR', NEWID());")
$lines.Add("")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'MANAGER')")
$lines.Add(" INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)")
$lines.Add(" VALUES ('$roleManager', 'Manager', 'MANAGER', NEWID());")
$lines.Add("")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'EMPLOYEE')")
$lines.Add(" INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)")
$lines.Add(" VALUES ('$roleEmployee', 'Employee', 'EMPLOYEE', NEWID());")
$lines.Add("")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'SHOPFLOOR')")
$lines.Add(" INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)")
$lines.Add(" VALUES ('$roleShopFloor', 'ShopFloor', 'SHOPFLOOR', NEWID());")
$lines.Add("")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'READONLY')")
$lines.Add(" INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)")
$lines.Add(" VALUES ('$roleReadOnly', 'ReadOnly', 'READONLY', NEWID());")
$lines.Add("")
$lines.Add("PRINT 'Roles inserted.';")
$lines.Add("GO")
$lines.Add("")
$lines.Add("-- 3. SuperAdmin Users")
$lines.Add("")
$lines.Add("-- User 1: superadmin@powdercoating.com / SuperAdmin123!")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetUsers WHERE NormalizedEmail = 'SUPERADMIN@POWDERCOATING.COM')")
$lines.Add("BEGIN")
$lines.Add(" INSERT INTO AspNetUsers (")
$lines.Add(" Id, UserName, NormalizedUserName, Email, NormalizedEmail,")
$lines.Add(" EmailConfirmed, PasswordHash, SecurityStamp, ConcurrencyStamp,")
$lines.Add(" PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled,")
$lines.Add(" LockoutEnd, LockoutEnabled, AccessFailedCount,")
$lines.Add(" CompanyId, CompanyRole,")
$lines.Add(" FirstName, LastName, EmployeeNumber,")
$lines.Add(" HireDate, IsActive,")
$lines.Add(" Department, Position, HourlyRate,")
$lines.Add(" Theme, DateFormat, TimeZone, SidebarColor,")
$lines.Add(" CanViewShopFloor, CanManageJobs, CanManageInventory, CanManageCustomers,")
$lines.Add(" CanCreateQuotes, CanApproveQuotes, CanManageCalendar, CanViewCalendar,")
$lines.Add(" CanManageProducts, CanViewProducts, CanManageEquipment,")
$lines.Add(" CanManageSuppliers, CanManageMaintenance,")
$lines.Add(" CreatedAt")
$lines.Add(" ) VALUES (")
$lines.Add(" '$user1Id',")
$lines.Add(" 'superadmin@powdercoating.com', 'SUPERADMIN@POWDERCOATING.COM',")
$lines.Add(" 'superadmin@powdercoating.com', 'SUPERADMIN@POWDERCOATING.COM',")
$lines.Add(" 1, '$hash1', '$user1Security', '$user1Stamp',")
$lines.Add(" NULL, 0, 0, NULL, 1, 0,")
$lines.Add(" 1, NULL,")
$lines.Add(" 'Super', 'Admin', 'SA-001',")
$lines.Add(" '$now', 1,")
$lines.Add(" 'Platform', 'Super Administrator', 0,")
$lines.Add(" 'light', 'MM/dd/yyyy', 'America/New_York', 'ocean',")
$lines.Add(" 1, 1, 1, 1,")
$lines.Add(" 1, 1, 1, 1,")
$lines.Add(" 1, 1, 1,")
$lines.Add(" 1, 1,")
$lines.Add(" '$now'")
$lines.Add(" );")
$lines.Add(" PRINT 'User superadmin@powdercoating.com inserted.';")
$lines.Add("END")
$lines.Add("ELSE")
$lines.Add(" PRINT 'User superadmin@powdercoating.com already exists - skipped.';")
$lines.Add("GO")
$lines.Add("")
$lines.Add("-- User 2: admin@powdercoating.com / Admin123!")
$lines.Add("IF NOT EXISTS (SELECT 1 FROM AspNetUsers WHERE NormalizedEmail = 'ADMIN@POWDERCOATING.COM')")
$lines.Add("BEGIN")
$lines.Add(" INSERT INTO AspNetUsers (")
$lines.Add(" Id, UserName, NormalizedUserName, Email, NormalizedEmail,")
$lines.Add(" EmailConfirmed, PasswordHash, SecurityStamp, ConcurrencyStamp,")
$lines.Add(" PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled,")
$lines.Add(" LockoutEnd, LockoutEnabled, AccessFailedCount,")
$lines.Add(" CompanyId, CompanyRole,")
$lines.Add(" FirstName, LastName, EmployeeNumber,")
$lines.Add(" HireDate, IsActive,")
$lines.Add(" Department, Position, HourlyRate,")
$lines.Add(" Theme, DateFormat, TimeZone, SidebarColor,")
$lines.Add(" CanViewShopFloor, CanManageJobs, CanManageInventory, CanManageCustomers,")
$lines.Add(" CanCreateQuotes, CanApproveQuotes, CanManageCalendar, CanViewCalendar,")
$lines.Add(" CanManageProducts, CanViewProducts, CanManageEquipment,")
$lines.Add(" CanManageSuppliers, CanManageMaintenance,")
$lines.Add(" CreatedAt")
$lines.Add(" ) VALUES (")
$lines.Add(" '$user2Id',")
$lines.Add(" 'admin@powdercoating.com', 'ADMIN@POWDERCOATING.COM',")
$lines.Add(" 'admin@powdercoating.com', 'ADMIN@POWDERCOATING.COM',")
$lines.Add(" 1, '$hash2', '$user2Security', '$user2Stamp',")
$lines.Add(" NULL, 0, 0, NULL, 1, 0,")
$lines.Add(" 1, NULL,")
$lines.Add(" 'Admin', 'User', 'SA-002',")
$lines.Add(" '$now', 1,")
$lines.Add(" 'Platform', 'Platform Administrator', 0,")
$lines.Add(" 'light', 'MM/dd/yyyy', 'America/New_York', 'ocean',")
$lines.Add(" 1, 1, 1, 1,")
$lines.Add(" 1, 1, 1, 1,")
$lines.Add(" 1, 1, 1,")
$lines.Add(" 1, 1,")
$lines.Add(" '$now'")
$lines.Add(" );")
$lines.Add(" PRINT 'User admin@powdercoating.com inserted.';")
$lines.Add("END")
$lines.Add("ELSE")
$lines.Add(" PRINT 'User admin@powdercoating.com already exists - skipped.';")
$lines.Add("GO")
$lines.Add("")
$lines.Add("-- 4. Assign SuperAdmin role to both users")
$lines.Add("")
$lines.Add("DECLARE @roleId NVARCHAR(450) = (SELECT Id FROM AspNetRoles WHERE NormalizedName = 'SUPERADMIN');")
$lines.Add("DECLARE @u1Id NVARCHAR(450) = (SELECT Id FROM AspNetUsers WHERE NormalizedEmail = 'SUPERADMIN@POWDERCOATING.COM');")
$lines.Add("DECLARE @u2Id NVARCHAR(450) = (SELECT Id FROM AspNetUsers WHERE NormalizedEmail = 'ADMIN@POWDERCOATING.COM');")
$lines.Add("")
$lines.Add("IF @u1Id IS NOT NULL AND @roleId IS NOT NULL")
$lines.Add(" AND NOT EXISTS (SELECT 1 FROM AspNetUserRoles WHERE UserId = @u1Id AND RoleId = @roleId)")
$lines.Add("BEGIN")
$lines.Add(" INSERT INTO AspNetUserRoles (UserId, RoleId) VALUES (@u1Id, @roleId);")
$lines.Add(" PRINT 'Role assigned to superadmin@powdercoating.com.';")
$lines.Add("END")
$lines.Add("")
$lines.Add("IF @u2Id IS NOT NULL AND @roleId IS NOT NULL")
$lines.Add(" AND NOT EXISTS (SELECT 1 FROM AspNetUserRoles WHERE UserId = @u2Id AND RoleId = @roleId)")
$lines.Add("BEGIN")
$lines.Add(" INSERT INTO AspNetUserRoles (UserId, RoleId) VALUES (@u2Id, @roleId);")
$lines.Add(" PRINT 'Role assigned to admin@powdercoating.com.';")
$lines.Add("END")
$lines.Add("GO")
$lines.Add("")
$lines.Add("PRINT '================================================';")
$lines.Add("PRINT 'Seed complete.';")
$lines.Add("PRINT 'Login 1: superadmin@powdercoating.com / SuperAdmin123!';")
$lines.Add("PRINT 'Login 2: admin@powdercoating.com / Admin123!';")
$lines.Add("PRINT '================================================';")
$lines.Add("GO")
$lines | Set-Content -Path $outFile -Encoding UTF8
Write-Host ""
Write-Host "SQL file written to: $outFile" -ForegroundColor Green
Write-Host ""
Write-Host "Run it against your Azure SQL database using:" -ForegroundColor Yellow
Write-Host " sqlcmd -S <server>.database.windows.net -d PowderCoatingDb -U <user> -P <password> -i seed_admins.sql" -ForegroundColor White
Write-Host " OR paste it into Azure Portal > Query Editor" -ForegroundColor White
Write-Host ""
Write-Host "Login 1: superadmin@powdercoating.com / SuperAdmin123!" -ForegroundColor Cyan
Write-Host "Login 2: admin@powdercoating.com / Admin123!" -ForegroundColor Cyan
+211
View File
@@ -0,0 +1,211 @@
-- ============================================================================
-- HARD DELETE ALL COMPANY DATA - Demo Company Only
-- WARNING: This permanently deletes all data for the specified company!
-- ============================================================================
SET NOCOUNT ON;
DECLARE @CompanyId INT = 1; -- Demo Company (change if needed)
DECLARE @CompanyName NVARCHAR(255);
DECLARE @RowsDeleted INT = 0;
-- Get company name for confirmation
SELECT @CompanyName = CompanyName FROM Companies WHERE Id = @CompanyId;
PRINT '============================================================================';
PRINT 'HARD DELETE - Company: ' + ISNULL(@CompanyName, 'NOT FOUND');
PRINT 'Company ID: ' + CAST(@CompanyId AS NVARCHAR(10));
PRINT 'Started at: ' + CONVERT(NVARCHAR(30), GETDATE(), 120);
PRINT '============================================================================';
PRINT '';
IF @CompanyName IS NULL
BEGIN
PRINT 'ERROR: Company not found!';
RETURN;
END
BEGIN TRANSACTION;
BEGIN TRY
-- ========================================================================
-- LEVEL 1: Most dependent tables (delete first)
-- ========================================================================
PRINT 'Deleting Level 1: Most dependent tables...';
-- JobStatusHistory (references Jobs)
DELETE FROM JobStatusHistory WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ JobStatusHistory: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- JobPhotos (references Jobs)
DELETE FROM JobPhotos WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ JobPhotos: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- JobNotes (references Jobs)
DELETE FROM JobNotes WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ JobNotes: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- Appointments (references Jobs, Customers)
DELETE FROM Appointments WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ Appointments: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- JobItems (references Jobs)
DELETE FROM JobItems WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ JobItems: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- QuoteItems (references Quotes)
DELETE FROM QuoteItems WHERE QuoteId IN (SELECT Id FROM Quotes WHERE CompanyId = @CompanyId);
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ QuoteItems: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- InventoryTransactions (references InventoryItems)
DELETE FROM InventoryTransactions WHERE InventoryItemId IN (SELECT Id FROM InventoryItems WHERE CompanyId = @CompanyId);
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ InventoryTransactions: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- MaintenanceRecords (references Equipment)
DELETE FROM MaintenanceRecords WHERE EquipmentId IN (SELECT Id FROM Equipment WHERE CompanyId = @CompanyId);
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ MaintenanceRecords: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
PRINT '';
-- ========================================================================
-- LEVEL 2: Mid-level dependencies
-- ========================================================================
PRINT 'Deleting Level 2: Mid-level dependencies...';
-- Jobs (references Customers, Quotes)
DELETE FROM Jobs WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ Jobs: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- Quotes (references Customers)
DELETE FROM Quotes WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ Quotes: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- CatalogItems (references CatalogCategories, Customers)
DELETE FROM CatalogItems WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ CatalogItems: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
PRINT '';
-- ========================================================================
-- LEVEL 3: Base entities
-- ========================================================================
PRINT 'Deleting Level 3: Base entities...';
-- Customers
DELETE FROM Customers WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ Customers: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- InventoryItems
DELETE FROM InventoryItems WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ InventoryItems: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- Equipment
DELETE FROM Equipment WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ Equipment: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- CatalogCategories
DELETE FROM CatalogCategories WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ CatalogCategories: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- ShopWorkers
DELETE FROM ShopWorkers WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ ShopWorkers: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- PricingTiers
DELETE FROM PricingTiers WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ PricingTiers: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- CompanyOperatingCosts
DELETE FROM CompanyOperatingCosts WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ CompanyOperatingCosts: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- Suppliers
DELETE FROM Suppliers WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ Suppliers: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
PRINT '';
-- ========================================================================
-- LEVEL 4: Lookup tables (delete last)
-- ========================================================================
PRINT 'Deleting Level 4: Lookup tables...';
-- JobStatusLookup
DELETE FROM JobStatusLookup WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ JobStatusLookup: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- JobPriorityLookup
DELETE FROM JobPriorityLookup WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ JobPriorityLookup: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- QuoteStatusLookup
DELETE FROM QuoteStatusLookup WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ QuoteStatusLookup: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- AppointmentStatusLookup
DELETE FROM AppointmentStatusLookup WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ AppointmentStatusLookup: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- AppointmentTypeLookup
DELETE FROM AppointmentTypeLookup WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ AppointmentTypeLookup: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
-- InventoryCategoryLookup
DELETE FROM InventoryCategoryLookup WHERE CompanyId = @CompanyId;
SET @RowsDeleted = @@ROWCOUNT;
PRINT ' ✓ InventoryCategoryLookup: ' + CAST(@RowsDeleted AS NVARCHAR(10)) + ' rows';
PRINT '';
PRINT '============================================================================';
PRINT 'SUCCESS! All company data deleted.';
PRINT 'Completed at: ' + CONVERT(NVARCHAR(30), GETDATE(), 120);
PRINT '============================================================================';
COMMIT TRANSACTION;
PRINT '';
PRINT 'Transaction committed successfully.';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
PRINT '';
PRINT '============================================================================';
PRINT 'ERROR! Rolling back transaction...';
PRINT '';
PRINT 'Error Message: ' + ERROR_MESSAGE();
PRINT 'Error Line: ' + CAST(ERROR_LINE() AS NVARCHAR(10));
PRINT '============================================================================';
-- Re-throw the error
THROW;
END CATCH;
SET NOCOUNT OFF;
+27
View File
@@ -0,0 +1,27 @@
-- Find what's blocking the delete
SELECT
session_id,
blocking_session_id,
wait_type,
wait_time,
last_wait_type,
command,
status
FROM sys.dm_exec_requests
WHERE blocking_session_id <> 0;
-- See all active sessions on this database
SELECT
s.session_id,
s.login_name,
s.host_name,
s.program_name,
s.status,
r.command,
r.blocking_session_id
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
WHERE s.database_id = DB_ID('PowderCoatingDb');
-- To kill a specific session (replace XX with session_id):
-- KILL XX;
@@ -0,0 +1,67 @@
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
ALTER TABLE [Companies] ADD [AccountingOverride] bit NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
ALTER TABLE [Companies] ADD [OnlinePaymentsOverride] bit NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T17:41:53.0142151Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T17:41:53.0142159Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T17:41:53.0142161Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260412174157_AddCompanyFeatureOverrides', N'8.0.11');
END;
GO
COMMIT;
GO
@@ -0,0 +1,58 @@
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
ALTER TABLE [Companies] ADD [IsAnnualBilling] bit NOT NULL DEFAULT CAST(0 AS bit);
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T18:34:08.9093047Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T18:34:08.9093054Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T18:34:08.9093055Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260412183411_AddIsAnnualBilling', N'8.0.11');
END;
GO
COMMIT;
GO
@@ -0,0 +1,63 @@
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Key', N'Value', N'Label', N'Description', N'GroupName') AND [object_id] = OBJECT_ID(N'[PlatformSettings]'))
SET IDENTITY_INSERT [PlatformSettings] ON;
EXEC(N'INSERT INTO [PlatformSettings] ([Id], [Key], [Value], [Label], [Description], [GroupName])
VALUES (8, N''MaxTenants'', N'''', N''Max Tenants'', N''Maximum number of tenant companies allowed to self-register. Leave blank or 0 for unlimited. Once the limit is reached, the signup page and login signup link are hidden.'', N''Access Control'')');
IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Key', N'Value', N'Label', N'Description', N'GroupName') AND [object_id] = OBJECT_ID(N'[PlatformSettings]'))
SET IDENTITY_INSERT [PlatformSettings] OFF;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T00:52:25.3071531Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T00:52:25.3071537Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T00:52:25.3071538Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260412005228_AddMaxTenantsSetting', N'8.0.11');
END;
GO
COMMIT;
GO
@@ -0,0 +1,71 @@
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
CREATE TABLE [PendingRegistrationSessions] (
[Id] int NOT NULL IDENTITY,
[Token] nvarchar(max) NOT NULL,
[CompanyName] nvarchar(max) NOT NULL,
[CompanyPhone] nvarchar(max) NULL,
[FirstName] nvarchar(max) NOT NULL,
[LastName] nvarchar(max) NOT NULL,
[Email] nvarchar(max) NOT NULL,
[Plan] int NOT NULL,
[IsAnnual] bit NOT NULL,
[CreatedAt] datetime2 NOT NULL,
[IsCompleted] bit NOT NULL,
CONSTRAINT [PK_PendingRegistrationSessions] PRIMARY KEY ([Id])
);
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-14T13:58:07.0916607Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-14T13:58:07.0916613Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-14T13:58:07.0916614Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260414135810_AddPendingRegistrationSession', N'8.0.11');
END;
GO
COMMIT;
GO
@@ -0,0 +1,76 @@
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
ALTER TABLE [CompanyPreferences] ADD [SetupWizardCompletedAt] datetime2 NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
ALTER TABLE [CompanyPreferences] ADD [SetupWizardCompletedByName] nvarchar(max) NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
ALTER TABLE [CompanyPreferences] ADD [SetupWizardCompletedByUserId] nvarchar(max) NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-15T01:02:00.3083161Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-15T01:02:00.3083167Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-15T01:02:00.3083169Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260415010203_AddSetupWizardCompletionTracking', N'8.0.11');
END;
GO
COMMIT;
GO
@@ -0,0 +1,335 @@
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Key', N'Value', N'Label', N'Description', N'GroupName') AND [object_id] = OBJECT_ID(N'[PlatformSettings]'))
SET IDENTITY_INSERT [PlatformSettings] ON;
EXEC(N'INSERT INTO [PlatformSettings] ([Id], [Key], [Value], [Label], [Description], [GroupName])
VALUES (8, N''MaxTenants'', N'''', N''Max Tenants'', N''Maximum number of tenant companies allowed to self-register. Leave blank or 0 for unlimited. Once the limit is reached, the signup page and login signup link are hidden.'', N''Access Control'')');
IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Key', N'Value', N'Label', N'Description', N'GroupName') AND [object_id] = OBJECT_ID(N'[PlatformSettings]'))
SET IDENTITY_INSERT [PlatformSettings] OFF;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T00:52:25.3071531Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T00:52:25.3071537Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T00:52:25.3071538Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412005228_AddMaxTenantsSetting'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260412005228_AddMaxTenantsSetting', N'8.0.11');
END;
GO
COMMIT;
GO
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
ALTER TABLE [Companies] ADD [AccountingOverride] bit NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
ALTER TABLE [Companies] ADD [OnlinePaymentsOverride] bit NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T17:41:53.0142151Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T17:41:53.0142159Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T17:41:53.0142161Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412174157_AddCompanyFeatureOverrides'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260412174157_AddCompanyFeatureOverrides', N'8.0.11');
END;
GO
COMMIT;
GO
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
ALTER TABLE [Companies] ADD [IsAnnualBilling] bit NOT NULL DEFAULT CAST(0 AS bit);
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T18:34:08.9093047Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T18:34:08.9093054Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-12T18:34:08.9093055Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260412183411_AddIsAnnualBilling'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260412183411_AddIsAnnualBilling', N'8.0.11');
END;
GO
COMMIT;
GO
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
CREATE TABLE [PendingRegistrationSessions] (
[Id] int NOT NULL IDENTITY,
[Token] nvarchar(max) NOT NULL,
[CompanyName] nvarchar(max) NOT NULL,
[CompanyPhone] nvarchar(max) NULL,
[FirstName] nvarchar(max) NOT NULL,
[LastName] nvarchar(max) NOT NULL,
[Email] nvarchar(max) NOT NULL,
[Plan] int NOT NULL,
[IsAnnual] bit NOT NULL,
[CreatedAt] datetime2 NOT NULL,
[IsCompleted] bit NOT NULL,
CONSTRAINT [PK_PendingRegistrationSessions] PRIMARY KEY ([Id])
);
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-14T13:58:07.0916607Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-14T13:58:07.0916613Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-14T13:58:07.0916614Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260414135810_AddPendingRegistrationSession'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260414135810_AddPendingRegistrationSession', N'8.0.11');
END;
GO
COMMIT;
GO
BEGIN TRANSACTION;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
ALTER TABLE [CompanyPreferences] ADD [SetupWizardCompletedAt] datetime2 NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
ALTER TABLE [CompanyPreferences] ADD [SetupWizardCompletedByName] nvarchar(max) NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
ALTER TABLE [CompanyPreferences] ADD [SetupWizardCompletedByUserId] nvarchar(max) NULL;
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-15T01:02:00.3083161Z''
WHERE [Id] = 1;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-15T01:02:00.3083167Z''
WHERE [Id] = 2;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
EXEC(N'UPDATE [PricingTiers] SET [CreatedAt] = ''2026-04-15T01:02:00.3083169Z''
WHERE [Id] = 3;
SELECT @@ROWCOUNT');
END;
GO
IF NOT EXISTS (
SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'20260415010203_AddSetupWizardCompletionTracking'
)
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260415010203_AddSetupWizardCompletionTracking', N'8.0.11');
END;
GO
COMMIT;
GO
+51
View File
@@ -0,0 +1,51 @@
-- ============================================================================
-- NUCLEAR DELETE - Delete ALL data for a company (including fresh seeds)
-- Run this, then immediately seed WITHOUT restarting the app
-- ============================================================================
DECLARE @CompanyId INT = 1;
PRINT 'NUCLEAR DELETE - Deleting EVERYTHING for Company ID: ' + CAST(@CompanyId AS NVARCHAR(10));
PRINT '';
BEGIN TRANSACTION;
BEGIN TRY
-- Delete in reverse order (most dependent first)
DELETE FROM JobStatusHistory WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM JobPhotos WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM JobNotes WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM Appointments WHERE CompanyId = @CompanyId;
DELETE FROM JobItems WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM QuoteItems WHERE QuoteId IN (SELECT Id FROM Quotes WHERE CompanyId = @CompanyId);
DELETE FROM InventoryTransactions WHERE InventoryItemId IN (SELECT Id FROM InventoryItems WHERE CompanyId = @CompanyId);
DELETE FROM MaintenanceRecords WHERE EquipmentId IN (SELECT Id FROM Equipment WHERE CompanyId = @CompanyId);
DELETE FROM Jobs WHERE CompanyId = @CompanyId;
DELETE FROM Quotes WHERE CompanyId = @CompanyId;
DELETE FROM CatalogItems WHERE CompanyId = @CompanyId;
DELETE FROM CatalogCategories WHERE CompanyId = @CompanyId;
DELETE FROM Customers WHERE CompanyId = @CompanyId;
DELETE FROM InventoryItems WHERE CompanyId = @CompanyId;
DELETE FROM Equipment WHERE CompanyId = @CompanyId;
DELETE FROM ShopWorkers WHERE CompanyId = @CompanyId;
DELETE FROM PricingTiers WHERE CompanyId = @CompanyId;
DELETE FROM CompanyOperatingCosts WHERE CompanyId = @CompanyId;
DELETE FROM Suppliers WHERE CompanyId = @CompanyId;
DELETE FROM JobStatusLookup WHERE CompanyId = @CompanyId;
DELETE FROM JobPriorityLookup WHERE CompanyId = @CompanyId;
DELETE FROM QuoteStatusLookup WHERE CompanyId = @CompanyId;
DELETE FROM AppointmentStatusLookup WHERE CompanyId = @CompanyId;
DELETE FROM AppointmentTypeLookup WHERE CompanyId = @CompanyId;
DELETE FROM InventoryCategoryLookup WHERE CompanyId = @CompanyId;
COMMIT TRANSACTION;
PRINT 'SUCCESS! All data deleted. NOW SEED IMMEDIATELY (do not restart app)!';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
PRINT 'ERROR: ' + ERROR_MESSAGE();
THROW;
END CATCH;
+230
View File
@@ -0,0 +1,230 @@
-- =============================================
-- Preview Company Data Reset
-- =============================================
-- This script shows what WOULD be deleted without actually deleting anything.
-- Use this to verify before running the actual ResetCompanyData.sql script.
-- =============================================
-- CONFIGURATION: Set ONE of these (comment out the other)
DECLARE @CompanyCode NVARCHAR(50) = 'DEMO'; -- Use company code
-- DECLARE @CompanyId INT = 1; -- OR use company ID directly
-- Get CompanyId from CompanyCode if needed
IF @CompanyCode IS NOT NULL AND EXISTS(SELECT 1 FROM Companies WHERE CompanyCode = @CompanyCode)
BEGIN
SELECT @CompanyId = Id FROM Companies WHERE CompanyCode = @CompanyCode;
END
-- Validate company exists
IF @CompanyId IS NULL OR NOT EXISTS(SELECT 1 FROM Companies WHERE Id = @CompanyId)
BEGIN
RAISERROR('Company not found! Please check @CompanyId or @CompanyCode', 16, 1);
RETURN;
END
-- Display company information
DECLARE @CompanyName NVARCHAR(200);
SELECT @CompanyName = CompanyName, @CompanyCode = CompanyCode FROM Companies WHERE Id = @CompanyId;
PRINT '=============================================';
PRINT 'PREVIEW: DATA TO BE DELETED FOR COMPANY:';
PRINT ' Company ID: ' + CAST(@CompanyId AS NVARCHAR(10));
PRINT ' Company Code: ' + @CompanyCode;
PRINT ' Company Name: ' + @CompanyName;
PRINT '=============================================';
PRINT '';
PRINT 'NOTE: This is a PREVIEW only. No data will be deleted.';
PRINT '';
DECLARE @Count INT;
DECLARE @TotalCount INT = 0;
-- Job Photos
SELECT @Count = COUNT(*)
FROM JobPhotos
WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
PRINT 'Job Photos: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Job Notes
SELECT @Count = COUNT(*)
FROM JobNotes
WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
PRINT 'Job Notes: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Job Items
SELECT @Count = COUNT(*)
FROM JobItems
WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
PRINT 'Job Items: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Jobs
SELECT @Count = COUNT(*) FROM Jobs WHERE CompanyId = @CompanyId;
PRINT 'Jobs: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Quote Items
SELECT @Count = COUNT(*)
FROM QuoteItems
WHERE QuoteId IN (SELECT Id FROM Quotes WHERE CompanyId = @CompanyId);
PRINT 'Quote Items: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Quotes
SELECT @Count = COUNT(*) FROM Quotes WHERE CompanyId = @CompanyId;
PRINT 'Quotes: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Appointments
IF OBJECT_ID('Appointments', 'U') IS NOT NULL
BEGIN
SELECT @Count = COUNT(*) FROM Appointments WHERE CompanyId = @CompanyId;
PRINT 'Appointments: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
END
-- Inventory Transactions
SELECT @Count = COUNT(*)
FROM InventoryTransactions
WHERE InventoryItemId IN (SELECT Id FROM InventoryItems WHERE CompanyId = @CompanyId);
PRINT 'Inventory Transactions: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Inventory Items
SELECT @Count = COUNT(*) FROM InventoryItems WHERE CompanyId = @CompanyId;
PRINT 'Inventory Items: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Maintenance Records
SELECT @Count = COUNT(*)
FROM MaintenanceRecords
WHERE EquipmentId IN (SELECT Id FROM Equipment WHERE CompanyId = @CompanyId);
PRINT 'Maintenance Records: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Equipment
SELECT @Count = COUNT(*) FROM Equipment WHERE CompanyId = @CompanyId;
PRINT 'Equipment: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Catalog Items
IF OBJECT_ID('CatalogItems', 'U') IS NOT NULL
BEGIN
SELECT @Count = COUNT(*) FROM CatalogItems WHERE CompanyId = @CompanyId;
PRINT 'Catalog Items: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
END
-- Catalog Categories
IF OBJECT_ID('CatalogCategories', 'U') IS NOT NULL
BEGIN
SELECT @Count = COUNT(*) FROM CatalogCategories WHERE CompanyId = @CompanyId;
PRINT 'Catalog Categories: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
END
-- Suppliers
IF OBJECT_ID('Suppliers', 'U') IS NOT NULL
BEGIN
SELECT @Count = COUNT(*) FROM Suppliers WHERE CompanyId = @CompanyId;
PRINT 'Suppliers: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
END
-- Customers
SELECT @Count = COUNT(*) FROM Customers WHERE CompanyId = @CompanyId;
PRINT 'Customers: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Pricing Tiers
SELECT @Count = COUNT(*) FROM PricingTiers WHERE CompanyId = @CompanyId;
PRINT 'Pricing Tiers: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Company Operating Costs
SELECT @Count = COUNT(*) FROM CompanyOperatingCosts WHERE CompanyId = @CompanyId;
PRINT 'Company Operating Costs: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
-- Company Preferences
IF OBJECT_ID('CompanyPreferences', 'U') IS NOT NULL
BEGIN
SELECT @Count = COUNT(*) FROM CompanyPreferences WHERE CompanyId = @CompanyId;
PRINT 'Company Preferences: ' + CAST(@Count AS NVARCHAR(10));
SET @TotalCount = @TotalCount + @Count;
END
PRINT '';
PRINT '=============================================';
PRINT 'TOTAL RECORDS TO BE DELETED: ' + CAST(@TotalCount AS NVARCHAR(10));
PRINT '=============================================';
PRINT '';
PRINT 'PRESERVED DATA (will NOT be deleted):';
PRINT ' - Company record: ' + @CompanyName;
PRINT ' - Users associated with company';
PRINT ' - Job Status Lookups';
PRINT ' - Job Priority Lookups';
PRINT ' - Quote Status Lookups';
PRINT ' - Inventory Category Lookups';
PRINT ' - Appointment Status Lookups';
PRINT ' - Appointment Type Lookups';
PRINT '';
-- Show sample data for verification
PRINT '=============================================';
PRINT 'SAMPLE DATA (first 5 of each type):';
PRINT '=============================================';
PRINT '';
-- Sample Customers
IF EXISTS(SELECT 1 FROM Customers WHERE CompanyId = @CompanyId)
BEGIN
PRINT 'CUSTOMERS:';
SELECT TOP 5
Id,
CompanyName,
ISNULL(ContactFirstName + ' ' + ContactLastName, 'N/A') AS ContactName,
Email
FROM Customers
WHERE CompanyId = @CompanyId
ORDER BY Id;
PRINT '';
END
-- Sample Jobs
IF EXISTS(SELECT 1 FROM Jobs WHERE CompanyId = @CompanyId)
BEGIN
PRINT 'JOBS:';
SELECT TOP 5
j.Id,
j.JobNumber,
c.CompanyName AS CustomerName,
j.Status,
j.CreatedAt
FROM Jobs j
LEFT JOIN Customers c ON j.CustomerId = c.Id
WHERE j.CompanyId = @CompanyId
ORDER BY j.Id;
PRINT '';
END
-- Sample Inventory
IF EXISTS(SELECT 1 FROM InventoryItems WHERE CompanyId = @CompanyId)
BEGIN
PRINT 'INVENTORY ITEMS:';
SELECT TOP 5
Id,
SKU,
Name,
QuantityOnHand,
UnitOfMeasure
FROM InventoryItems
WHERE CompanyId = @CompanyId
ORDER BY Id;
PRINT '';
END
PRINT '';
PRINT 'To proceed with deletion, run: ResetCompanyData.sql';
+34
View File
@@ -0,0 +1,34 @@
-- Quick delete without transaction - use if NuclearDelete hangs
DECLARE @CompanyId INT = 1;
-- Most dependent first
DELETE FROM JobStatusHistory WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM JobPhotos WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM JobNotes WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM Appointments WHERE CompanyId = @CompanyId;
DELETE FROM JobItems WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
DELETE FROM QuoteItems WHERE QuoteId IN (SELECT Id FROM Quotes WHERE CompanyId = @CompanyId);
DELETE FROM InventoryTransactions WHERE InventoryItemId IN (SELECT Id FROM InventoryItems WHERE CompanyId = @CompanyId);
DELETE FROM MaintenanceRecords WHERE EquipmentId IN (SELECT Id FROM Equipment WHERE CompanyId = @CompanyId);
DELETE FROM Jobs WHERE CompanyId = @CompanyId;
DELETE FROM Quotes WHERE CompanyId = @CompanyId;
DELETE FROM CatalogItems WHERE CompanyId = @CompanyId;
DELETE FROM CatalogCategories WHERE CompanyId = @CompanyId;
DELETE FROM Customers WHERE CompanyId = @CompanyId;
DELETE FROM InventoryItems WHERE CompanyId = @CompanyId;
DELETE FROM Equipment WHERE CompanyId = @CompanyId;
DELETE FROM ShopWorkers WHERE CompanyId = @CompanyId;
DELETE FROM PricingTiers WHERE CompanyId = @CompanyId;
DELETE FROM CompanyOperatingCosts WHERE CompanyId = @CompanyId;
DELETE FROM Suppliers WHERE CompanyId = @CompanyId;
DELETE FROM JobStatusLookups WHERE CompanyId = @CompanyId;
DELETE FROM JobPriorityLookups WHERE CompanyId = @CompanyId;
DELETE FROM QuoteStatusLookups WHERE CompanyId = @CompanyId;
DELETE FROM AppointmentStatusLookups WHERE CompanyId = @CompanyId;
DELETE FROM AppointmentTypeLookups WHERE CompanyId = @CompanyId;
DELETE FROM InventoryCategoryLookups WHERE CompanyId = @CompanyId;
PRINT 'Done!';
+187
View File
@@ -0,0 +1,187 @@
# Company Data Reset Scripts
These scripts allow you to completely reset a company's transactional data for testing purposes while preserving lookup tables and the company structure.
## Files
- **`PreviewCompanyDataReset.sql`** - Shows what would be deleted without making changes (safe to run)
- **`ResetCompanyData.sql`** - Actually deletes the data (use with caution!)
## What Gets Deleted
The scripts remove ALL transactional data for the specified company:
- ✓ Jobs, Job Items, Job Notes, Job Photos
- ✓ Quotes and Quote Items
- ✓ Customers
- ✓ Inventory Items and Inventory Transactions
- ✓ Equipment and Maintenance Records
- ✓ Catalog Categories and Catalog Items
- ✓ Suppliers
- ✓ Appointments
- ✓ Pricing Tiers
- ✓ Company Operating Costs
- ✓ Company Preferences
## What Gets Preserved
The scripts preserve all lookup tables and structure:
- ✓ The Company record itself
- ✓ Users associated with the company
- ✓ Job Status Lookups
- ✓ Job Priority Lookups
- ✓ Quote Status Lookups
- ✓ Inventory Category Lookups
- ✓ Appointment Status Lookups
- ✓ Appointment Type Lookups
## Usage
### Step 1: Preview (Recommended)
First, run the preview script to see what will be deleted:
1. Open `PreviewCompanyDataReset.sql` in SQL Server Management Studio (SSMS) or Azure Data Studio
2. Set the company identifier at the top of the script:
```sql
-- Option 1: Use company code
DECLARE @CompanyCode NVARCHAR(50) = 'DEMO';
-- Option 2: OR use company ID (comment out the line above)
-- DECLARE @CompanyId INT = 1;
```
3. Execute the script
4. Review the output showing:
- Count of records to be deleted per table
- Total records to be deleted
- Sample data from each table
- What will be preserved
### Step 2: Reset (Actual Deletion)
After reviewing the preview, run the reset script:
1. Open `ResetCompanyData.sql`
2. Set the same company identifier:
```sql
DECLARE @CompanyCode NVARCHAR(50) = 'DEMO';
```
3. **IMPORTANT:** Make a database backup if needed
4. Execute the script
5. Review the output showing:
- Deleted record counts per table
- Total deleted records
- Preserved data
### Step 3: Re-seed (Optional)
After resetting, you can re-populate the company with fresh demo data:
1. Log in to the application as a SuperAdmin
2. Navigate to: **Platform Management** > **Seed Data**
3. Click "Seed Data" for the company you just reset
4. Review the seeding results and any warnings
## Safety Features
### Transaction Rollback
Both scripts use transactions. If any error occurs during deletion, ALL changes are rolled back automatically.
### Validation
- Scripts validate that the company exists before attempting deletion
- Clear error messages if company is not found
### Detailed Logging
- Each deletion step is logged with record counts
- Final summary shows exactly what was deleted
- Preserved data is clearly listed
## Examples
### Reset the Demo Company
```sql
DECLARE @CompanyCode NVARCHAR(50) = 'DEMO';
```
### Reset a Specific Company by ID
```sql
-- DECLARE @CompanyCode NVARCHAR(50) = 'DEMO'; -- Comment this out
DECLARE @CompanyId INT = 5;
```
### Reset Multiple Companies
To reset multiple companies, execute the script once for each company (change the company code/ID between runs).
## Common Scenarios
### Testing Fresh Import
```
1. Run ResetCompanyData.sql for company "ABC"
2. Import CSV data for company ABC
3. Test the import results
```
### Testing Seed Data Changes
```
1. Modify seed data routines in code
2. Run ResetCompanyData.sql for "DEMO"
3. Re-seed via UI to test new seed data
4. Verify results
```
### Clean Slate for Demo
```
1. Preview what will be deleted
2. Run ResetCompanyData.sql
3. Re-seed with fresh demo data
4. Demo is ready with clean data
```
## Warnings
⚠️ **IMPORTANT:** These scripts permanently delete data!
- Always run the **preview script first**
- Make a **database backup** before running reset on production data
- Cannot be undone (unless you restore from backup)
- Users are NOT deleted (they will exist but have no data)
- Company configuration is NOT deleted
## Database Permissions
To run these scripts, you need:
- `db_datawriter` role (to delete data)
- `db_datareader` role (to read company info)
- Execute permissions on the database
## Troubleshooting
### Foreign Key Constraint Error
If you get a foreign key violation, the script may need updating for new tables. Check the error message for the table name and add it to the script in the correct order.
### Company Not Found
```
Company not found! Please check @CompanyId or @CompanyCode
```
**Solution:** Verify the company code or ID is correct:
```sql
SELECT Id, CompanyCode, CompanyName FROM Companies;
```
### Transaction Rollback
If the script fails and rolls back:
1. Check the error message in the output
2. Fix the issue (usually a missing table or new FK constraint)
3. Re-run the script
## Support
For questions or issues with these scripts:
1. Check the script comments for usage details
2. Review the CLAUDE.md file for database structure
3. Contact the development team
---
**Last Updated:** 2026-02-16
+243
View File
@@ -0,0 +1,243 @@
-- =============================================
-- Reset Company Data for Testing
-- =============================================
-- This script completely removes all transactional data for a company
-- while preserving lookup tables (statuses, priorities, categories, etc.)
--
-- Usage:
-- 1. Set the @CompanyId or @CompanyCode variable below
-- 2. Review the data to be deleted (uncomment SELECT statements)
-- 3. Execute the script
-- =============================================
-- CONFIGURATION: Set ONE of these (comment out the other)
DECLARE @CompanyCode NVARCHAR(50) = 'DEMO'; -- Use company code
-- DECLARE @CompanyId INT = 1; -- OR use company ID directly
-- Get CompanyId from CompanyCode if needed
IF @CompanyCode IS NOT NULL AND EXISTS(SELECT 1 FROM Companies WHERE CompanyCode = @CompanyCode)
BEGIN
SELECT @CompanyId = Id FROM Companies WHERE CompanyCode = @CompanyCode;
END
-- Validate company exists
IF @CompanyId IS NULL OR NOT EXISTS(SELECT 1 FROM Companies WHERE Id = @CompanyId)
BEGIN
RAISERROR('Company not found! Please check @CompanyId or @CompanyCode', 16, 1);
RETURN;
END
-- Display company information
DECLARE @CompanyName NVARCHAR(200);
SELECT @CompanyName = CompanyName, @CompanyCode = CompanyCode FROM Companies WHERE Id = @CompanyId;
PRINT '=============================================';
PRINT 'RESETTING DATA FOR COMPANY:';
PRINT ' Company ID: ' + CAST(@CompanyId AS NVARCHAR(10));
PRINT ' Company Code: ' + @CompanyCode;
PRINT ' Company Name: ' + @CompanyName;
PRINT '=============================================';
PRINT '';
-- Begin transaction for safety
BEGIN TRANSACTION;
BEGIN TRY
DECLARE @RowCount INT = 0;
DECLARE @TotalDeleted INT = 0;
-- =============================================
-- DELETE ORDER: Child tables first to avoid FK violations
-- =============================================
-- 1. Job Photos
PRINT 'Deleting Job Photos...';
DELETE FROM JobPhotos
WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' job photo(s)';
-- 2. Job Notes
PRINT 'Deleting Job Notes...';
DELETE FROM JobNotes
WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' job note(s)';
-- 3. Job Items
PRINT 'Deleting Job Items...';
DELETE FROM JobItems
WHERE JobId IN (SELECT Id FROM Jobs WHERE CompanyId = @CompanyId);
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' job item(s)';
-- 4. Jobs
PRINT 'Deleting Jobs...';
DELETE FROM Jobs WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' job(s)';
-- 5. Quote Items
PRINT 'Deleting Quote Items...';
DELETE FROM QuoteItems
WHERE QuoteId IN (SELECT Id FROM Quotes WHERE CompanyId = @CompanyId);
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' quote item(s)';
-- 6. Quotes
PRINT 'Deleting Quotes...';
DELETE FROM Quotes WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' quote(s)';
-- 7. Appointments (if table exists)
IF OBJECT_ID('Appointments', 'U') IS NOT NULL
BEGIN
PRINT 'Deleting Appointments...';
DELETE FROM Appointments WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' appointment(s)';
END
-- 8. Inventory Transactions
PRINT 'Deleting Inventory Transactions...';
DELETE FROM InventoryTransactions
WHERE InventoryItemId IN (SELECT Id FROM InventoryItems WHERE CompanyId = @CompanyId);
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' inventory transaction(s)';
-- 9. Inventory Items
PRINT 'Deleting Inventory Items...';
DELETE FROM InventoryItems WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' inventory item(s)';
-- 10. Maintenance Records
PRINT 'Deleting Maintenance Records...';
DELETE FROM MaintenanceRecords
WHERE EquipmentId IN (SELECT Id FROM Equipment WHERE CompanyId = @CompanyId);
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' maintenance record(s)';
-- 11. Equipment
PRINT 'Deleting Equipment...';
DELETE FROM Equipment WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' equipment record(s)';
-- 12. Catalog Items (if table exists)
IF OBJECT_ID('CatalogItems', 'U') IS NOT NULL
BEGIN
PRINT 'Deleting Catalog Items...';
DELETE FROM CatalogItems WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' catalog item(s)';
END
-- 13. Catalog Categories (if table exists)
IF OBJECT_ID('CatalogCategories', 'U') IS NOT NULL
BEGIN
PRINT 'Deleting Catalog Categories...';
DELETE FROM CatalogCategories WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' catalog categor(y/ies)';
END
-- 14. Suppliers (if table exists)
IF OBJECT_ID('Suppliers', 'U') IS NOT NULL
BEGIN
PRINT 'Deleting Suppliers...';
DELETE FROM Suppliers WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' supplier(s)';
END
-- 15. Customers
PRINT 'Deleting Customers...';
DELETE FROM Customers WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' customer(s)';
-- 16. Pricing Tiers
PRINT 'Deleting Pricing Tiers...';
DELETE FROM PricingTiers WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' pricing tier(s)';
-- 17. Company Operating Costs
PRINT 'Deleting Company Operating Costs...';
DELETE FROM CompanyOperatingCosts WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' operating cost record(s)';
-- 18. Company Preferences (if table exists)
IF OBJECT_ID('CompanyPreferences', 'U') IS NOT NULL
BEGIN
PRINT 'Deleting Company Preferences...';
DELETE FROM CompanyPreferences WHERE CompanyId = @CompanyId;
SET @RowCount = @@ROWCOUNT;
SET @TotalDeleted = @TotalDeleted + @RowCount;
PRINT ' Deleted ' + CAST(@RowCount AS NVARCHAR(10)) + ' preference record(s)';
END
-- =============================================
-- PRESERVED DATA (NOT DELETED)
-- =============================================
PRINT '';
PRINT 'PRESERVED DATA (not deleted):';
PRINT ' - Company record';
PRINT ' - Users associated with company';
PRINT ' - Job Status Lookups';
PRINT ' - Job Priority Lookups';
PRINT ' - Quote Status Lookups';
PRINT ' - Inventory Category Lookups';
PRINT ' - Appointment Status Lookups (if exists)';
PRINT ' - Appointment Type Lookups (if exists)';
PRINT '';
-- Summary
PRINT '=============================================';
PRINT 'RESET SUMMARY:';
PRINT ' Total records deleted: ' + CAST(@TotalDeleted AS NVARCHAR(10));
PRINT ' Company preserved: ' + @CompanyName + ' (' + @CompanyCode + ')';
PRINT '=============================================';
PRINT '';
-- Commit the transaction
PRINT 'Committing transaction...';
COMMIT TRANSACTION;
PRINT 'SUCCESS! Company data has been reset.';
PRINT '';
PRINT 'You can now re-seed the company data via:';
PRINT ' Platform Management > Seed Data > Seed Data for ' + @CompanyName;
END TRY
BEGIN CATCH
-- Rollback on error
PRINT '';
PRINT 'ERROR! Rolling back transaction...';
ROLLBACK TRANSACTION;
PRINT 'Error Message: ' + ERROR_MESSAGE();
PRINT 'Error Line: ' + CAST(ERROR_LINE() AS NVARCHAR(10));
-- Re-throw the error
THROW;
END CATCH;
+70
View File
@@ -0,0 +1,70 @@
-- ============================================================================
-- VERIFY COMPANY DATA - Check what records exist for a company
-- ============================================================================
DECLARE @CompanyId INT = 1; -- Demo Company
PRINT '============================================================================';
PRINT 'DATA VERIFICATION - Company ID: ' + CAST(@CompanyId AS NVARCHAR(10));
PRINT '============================================================================';
PRINT '';
-- Lookup Tables
PRINT 'LOOKUP TABLES:';
SELECT 'JobStatusLookup' AS TableName, COUNT(*) AS TotalRows, SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END) AS SoftDeleted
FROM JobStatusLookups WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'JobPriorityLookup', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM JobPriorityLookups WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'QuoteStatusLookup', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM QuoteStatusLookups WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'InventoryCategoryLookup', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM InventoryCategoryLookups WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'AppointmentStatusLookup', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM AppointmentStatusLookups WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'AppointmentTypeLookup', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM AppointmentTypeLookups WHERE CompanyId = @CompanyId;
PRINT '';
PRINT 'BASE ENTITIES:';
SELECT 'Customers' AS TableName, COUNT(*) AS TotalRows, SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END) AS SoftDeleted
FROM Customers WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'InventoryItems', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM InventoryItems WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'Equipment', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM Equipment WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'PricingTiers', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM PricingTiers WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'CompanyOperatingCosts', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM CompanyOperatingCosts WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'CatalogCategories', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM CatalogCategories WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'CatalogItems', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM CatalogItems WHERE CompanyId = @CompanyId;
PRINT '';
PRINT 'TRANSACTIONAL DATA:';
SELECT 'Quotes' AS TableName, COUNT(*) AS TotalRows, SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END) AS SoftDeleted
FROM Quotes WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'Jobs', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM Jobs WHERE CompanyId = @CompanyId
UNION ALL
SELECT 'Appointments', COUNT(*), SUM(CASE WHEN IsDeleted = 1 THEN 1 ELSE 0 END)
FROM Appointments WHERE CompanyId = @CompanyId;
PRINT '';
PRINT '============================================================================';
PRINT 'If TotalRows > 0, the hard delete did not work correctly!';
PRINT 'If SoftDeleted > 0, there are soft-deleted records blocking seeding!';
PRINT '============================================================================';
+269
View File
@@ -0,0 +1,269 @@
-- ============================================================
-- Lookup Table Migration Verification Script
-- ============================================================
-- This script verifies that the enum-to-lookup migration was successful
-- and that all data integrity has been preserved.
--
-- Run this against the PowderCoatingDb database
-- ============================================================
PRINT '========================================';
PRINT 'LOOKUP TABLE MIGRATION VERIFICATION';
PRINT '========================================';
PRINT '';
-- ============================================================
-- SECTION 1: Verify Lookup Tables Exist
-- ============================================================
PRINT '1. CHECKING LOOKUP TABLES EXIST...';
PRINT '';
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'JobStatusLookups')
PRINT ' ✓ JobStatusLookups table exists'
ELSE
PRINT ' ✗ ERROR: JobStatusLookups table NOT found!';
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'JobPriorityLookups')
PRINT ' ✓ JobPriorityLookups table exists'
ELSE
PRINT ' ✗ ERROR: JobPriorityLookups table NOT found!';
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'QuoteStatusLookups')
PRINT ' ✓ QuoteStatusLookups table exists'
ELSE
PRINT ' ✗ ERROR: QuoteStatusLookups table NOT found!';
PRINT '';
-- ============================================================
-- SECTION 2: Verify Lookup Data Counts
-- ============================================================
PRINT '2. CHECKING LOOKUP DATA COUNTS...';
PRINT '';
DECLARE @JobStatusCount INT;
DECLARE @JobPriorityCount INT;
DECLARE @QuoteStatusCount INT;
DECLARE @CompanyCount INT;
SELECT @CompanyCount = COUNT(*) FROM Companies WHERE IsDeleted = 0;
SELECT @JobStatusCount = COUNT(*) FROM JobStatusLookups WHERE IsDeleted = 0;
SELECT @JobPriorityCount = COUNT(*) FROM JobPriorityLookups WHERE IsDeleted = 0;
SELECT @QuoteStatusCount = COUNT(*) FROM QuoteStatusLookups WHERE IsDeleted = 0;
PRINT ' Companies: ' + CAST(@CompanyCount AS VARCHAR(10));
PRINT ' Job Statuses: ' + CAST(@JobStatusCount AS VARCHAR(10)) + ' (Expected: ' + CAST(@CompanyCount * 16 AS VARCHAR(10)) + ')';
PRINT ' Job Priorities: ' + CAST(@JobPriorityCount AS VARCHAR(10)) + ' (Expected: ' + CAST(@CompanyCount * 5 AS VARCHAR(10)) + ')';
PRINT ' Quote Statuses: ' + CAST(@QuoteStatusCount AS VARCHAR(10)) + ' (Expected: ' + CAST(@CompanyCount * 7 AS VARCHAR(10)) + ')';
IF @JobStatusCount = @CompanyCount * 16
PRINT ' ✓ Job Status count correct'
ELSE
PRINT ' ⚠ Job Status count mismatch (may be normal if custom statuses added)';
IF @JobPriorityCount = @CompanyCount * 5
PRINT ' ✓ Job Priority count correct'
ELSE
PRINT ' ⚠ Job Priority count mismatch (may be normal if custom priorities added)';
IF @QuoteStatusCount = @CompanyCount * 7
PRINT ' ✓ Quote Status count correct'
ELSE
PRINT ' ⚠ Quote Status count mismatch (may be normal if custom statuses added)';
PRINT '';
-- ============================================================
-- SECTION 3: Verify Foreign Key Relationships
-- ============================================================
PRINT '3. CHECKING FOREIGN KEY RELATIONSHIPS...';
PRINT '';
-- Check Jobs table has FK to JobStatusLookup
IF EXISTS (SELECT * FROM sys.foreign_keys WHERE name = 'FK_Jobs_JobStatusLookups_JobStatusId')
PRINT ' ✓ Jobs.JobStatusId FK exists'
ELSE
PRINT ' ✗ ERROR: Jobs.JobStatusId FK NOT found!';
-- Check Jobs table has FK to JobPriorityLookup
IF EXISTS (SELECT * FROM sys.foreign_keys WHERE name = 'FK_Jobs_JobPriorityLookups_JobPriorityId')
PRINT ' ✓ Jobs.JobPriorityId FK exists'
ELSE
PRINT ' ✗ ERROR: Jobs.JobPriorityId FK NOT found!';
-- Check Quotes table has FK to QuoteStatusLookup
IF EXISTS (SELECT * FROM sys.foreign_keys WHERE name = 'FK_Quotes_QuoteStatusLookups_QuoteStatusId')
PRINT ' ✓ Quotes.QuoteStatusId FK exists'
ELSE
PRINT ' ✗ ERROR: Quotes.QuoteStatusId FK NOT found!';
PRINT '';
-- ============================================================
-- SECTION 4: Verify Data Integrity - No Orphaned Records
-- ============================================================
PRINT '4. CHECKING DATA INTEGRITY...';
PRINT '';
-- Check for jobs with NULL status
DECLARE @JobsWithNullStatus INT;
SELECT @JobsWithNullStatus = COUNT(*) FROM Jobs WHERE JobStatusId IS NULL;
IF @JobsWithNullStatus = 0
PRINT ' ✓ All Jobs have valid JobStatusId'
ELSE
PRINT ' ✗ ERROR: ' + CAST(@JobsWithNullStatus AS VARCHAR(10)) + ' Jobs have NULL JobStatusId!';
-- Check for jobs with NULL priority
DECLARE @JobsWithNullPriority INT;
SELECT @JobsWithNullPriority = COUNT(*) FROM Jobs WHERE JobPriorityId IS NULL;
IF @JobsWithNullPriority = 0
PRINT ' ✓ All Jobs have valid JobPriorityId'
ELSE
PRINT ' ✗ ERROR: ' + CAST(@JobsWithNullPriority AS VARCHAR(10)) + ' Jobs have NULL JobPriorityId!';
-- Check for quotes with NULL status
DECLARE @QuotesWithNullStatus INT;
SELECT @QuotesWithNullStatus = COUNT(*) FROM Quotes WHERE QuoteStatusId IS NULL;
IF @QuotesWithNullStatus = 0
PRINT ' ✓ All Quotes have valid QuoteStatusId'
ELSE
PRINT ' ✗ ERROR: ' + CAST(@QuotesWithNullStatus AS VARCHAR(10)) + ' Quotes have NULL QuoteStatusId!';
-- Check for orphaned job status references
DECLARE @OrphanedJobStatuses INT;
SELECT @OrphanedJobStatuses = COUNT(*)
FROM Jobs j
LEFT JOIN JobStatusLookups s ON j.JobStatusId = s.Id
WHERE j.JobStatusId IS NOT NULL AND s.Id IS NULL;
IF @OrphanedJobStatuses = 0
PRINT ' ✓ No orphaned JobStatus references'
ELSE
PRINT ' ✗ ERROR: ' + CAST(@OrphanedJobStatuses AS VARCHAR(10)) + ' Jobs reference non-existent statuses!';
PRINT '';
-- ============================================================
-- SECTION 5: Verify Unique Constraints
-- ============================================================
PRINT '5. CHECKING UNIQUE CONSTRAINTS...';
PRINT '';
-- Check for duplicate status codes per company
DECLARE @DuplicateJobStatuses INT;
SELECT @DuplicateJobStatuses = COUNT(*)
FROM (
SELECT CompanyId, StatusCode, COUNT(*) as cnt
FROM JobStatusLookups
WHERE IsDeleted = 0
GROUP BY CompanyId, StatusCode
HAVING COUNT(*) > 1
) AS dupes;
IF @DuplicateJobStatuses = 0
PRINT ' ✓ No duplicate JobStatus codes per company'
ELSE
PRINT ' ✗ ERROR: ' + CAST(@DuplicateJobStatuses AS VARCHAR(10)) + ' duplicate JobStatus codes found!';
-- Check for duplicate priority codes per company
DECLARE @DuplicateJobPriorities INT;
SELECT @DuplicateJobPriorities = COUNT(*)
FROM (
SELECT CompanyId, PriorityCode, COUNT(*) as cnt
FROM JobPriorityLookups
WHERE IsDeleted = 0
GROUP BY CompanyId, PriorityCode
HAVING COUNT(*) > 1
) AS dupes;
IF @DuplicateJobPriorities = 0
PRINT ' ✓ No duplicate JobPriority codes per company'
ELSE
PRINT ' ✗ ERROR: ' + CAST(@DuplicateJobPriorities AS VARCHAR(10)) + ' duplicate JobPriority codes found!';
PRINT '';
-- ============================================================
-- SECTION 6: Verify Business Logic Flags
-- ============================================================
PRINT '6. CHECKING BUSINESS LOGIC FLAGS...';
PRINT '';
-- Check that each company has exactly one "Approved" quote status
DECLARE @CompaniesWithMultipleApproved INT;
SELECT @CompaniesWithMultipleApproved = COUNT(*)
FROM (
SELECT CompanyId, COUNT(*) as cnt
FROM QuoteStatusLookups
WHERE IsApprovedStatus = 1 AND IsDeleted = 0
GROUP BY CompanyId
HAVING COUNT(*) > 1
) AS dupes;
IF @CompaniesWithMultipleApproved = 0
PRINT ' ✓ Each company has exactly one Approved quote status'
ELSE
PRINT ' ⚠ WARNING: ' + CAST(@CompaniesWithMultipleApproved AS VARCHAR(10)) + ' companies have multiple Approved statuses!';
-- Check that system-defined statuses exist
DECLARE @SystemJobStatuses INT;
SELECT @SystemJobStatuses = COUNT(DISTINCT StatusCode)
FROM JobStatusLookups
WHERE IsSystemDefined = 1 AND StatusCode IN ('PENDING', 'COMPLETED', 'CANCELLED');
IF @SystemJobStatuses = 3
PRINT ' ✓ System-defined job statuses exist (PENDING, COMPLETED, CANCELLED)'
ELSE
PRINT ' ⚠ WARNING: Missing system-defined job statuses';
PRINT '';
-- ============================================================
-- SECTION 7: Sample Data Display
-- ============================================================
PRINT '7. SAMPLE LOOKUP DATA (First Company)...';
PRINT '';
-- Get first company ID
DECLARE @FirstCompanyId INT;
SELECT TOP 1 @FirstCompanyId = Id FROM Companies WHERE IsDeleted = 0 ORDER BY Id;
PRINT ' Job Statuses for Company ' + CAST(@FirstCompanyId AS VARCHAR(10)) + ':';
SELECT
' ' + CAST(DisplayOrder AS VARCHAR(3)) + '. ' +
DisplayName + ' (' + StatusCode + ') - ' +
ColorClass +
CASE WHEN IsSystemDefined = 1 THEN ' [SYSTEM]' ELSE '' END AS StatusInfo
FROM JobStatusLookups
WHERE CompanyId = @FirstCompanyId AND IsDeleted = 0
ORDER BY DisplayOrder;
PRINT '';
PRINT ' Job Priorities for Company ' + CAST(@FirstCompanyId AS VARCHAR(10)) + ':';
SELECT
' ' + CAST(DisplayOrder AS VARCHAR(3)) + '. ' +
DisplayName + ' (' + PriorityCode + ') - ' +
ColorClass AS PriorityInfo
FROM JobPriorityLookups
WHERE CompanyId = @FirstCompanyId AND IsDeleted = 0
ORDER BY DisplayOrder;
PRINT '';
PRINT ' Quote Statuses for Company ' + CAST(@FirstCompanyId AS VARCHAR(10)) + ':';
SELECT
' ' + CAST(DisplayOrder AS VARCHAR(3)) + '. ' +
DisplayName + ' (' + StatusCode + ') - ' +
ColorClass +
CASE WHEN IsApprovedStatus = 1 THEN ' [APPROVED]' ELSE '' END +
CASE WHEN IsConvertedStatus = 1 THEN ' [CONVERTED]' ELSE '' END AS StatusInfo
FROM QuoteStatusLookups
WHERE CompanyId = @FirstCompanyId AND IsDeleted = 0
ORDER BY DisplayOrder;
PRINT '';
PRINT '========================================';
PRINT 'VERIFICATION COMPLETE';
PRINT '========================================';
+36
View File
@@ -0,0 +1,36 @@
@echo off
echo ========================================
echo Apply Database Migrations
echo ========================================
echo.
cd ..\src\PowderCoating.Web
echo WARNING: This will modify your database!
echo Database: PowderCoatingDb on .\SQLEXPRESS
echo.
set /p confirm="Continue? (Y/N): "
if /i not "%confirm%"=="Y" (
echo Cancelled.
pause
exit /b
)
echo.
echo Applying migrations...
dotnet ef database update --project ..\PowderCoating.Infrastructure
if %ERRORLEVEL% EQU 0 (
echo.
echo ========================================
echo SUCCESS: Migrations applied
echo ========================================
) else (
echo.
echo ========================================
echo ERROR: Migration failed
echo ========================================
)
echo.
pause
+23
View File
@@ -0,0 +1,23 @@
@echo off
echo ========================================
echo Checking Database Migrations
echo ========================================
echo.
cd ..\src\PowderCoating.Web
echo Current migrations in project:
echo ----------------------------------------
dotnet ef migrations list --project ..\PowderCoating.Infrastructure
echo.
echo Database connection info:
echo ----------------------------------------
dotnet ef dbcontext info --project ..\PowderCoating.Infrastructure
echo.
echo.
echo To apply pending migrations, run:
echo .\scripts\apply-migrations.bat
echo.
pause
+135
View File
@@ -0,0 +1,135 @@
# Deployment Script for Development Server
# Run this script to deploy code changes and apply database migrations
param(
[switch]$SkipBuild,
[switch]$SkipMigrations,
[switch]$WhatIf
)
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Powder Coating App - Dev Deployment" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
$ErrorActionPreference = "Stop"
$projectRoot = Split-Path -Parent $PSScriptRoot
$webProject = Join-Path $projectRoot "src\PowderCoating.Web"
$infraProject = Join-Path $projectRoot "src\PowderCoating.Infrastructure"
# Step 1: Check current location
Write-Host "[1/5] Checking environment..." -ForegroundColor Yellow
if (-not (Test-Path $webProject)) {
Write-Host "ERROR: Web project not found at $webProject" -ForegroundColor Red
exit 1
}
Write-Host "✓ Project structure verified" -ForegroundColor Green
Write-Host ""
# Step 2: Build the solution
if (-not $SkipBuild) {
Write-Host "[2/5] Building solution..." -ForegroundColor Yellow
if ($WhatIf) {
Write-Host "WHAT-IF: Would run: dotnet build --configuration Release" -ForegroundColor Gray
} else {
Push-Location $projectRoot
dotnet build --configuration Release
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: Build failed" -ForegroundColor Red
Pop-Location
exit 1
}
Pop-Location
Write-Host "✓ Build successful" -ForegroundColor Green
}
} else {
Write-Host "[2/5] Skipping build (--SkipBuild specified)" -ForegroundColor Gray
}
Write-Host ""
# Step 3: Check for pending migrations
Write-Host "[3/5] Checking database migrations..." -ForegroundColor Yellow
if ($WhatIf) {
Write-Host "WHAT-IF: Would check migrations with: dotnet ef migrations list" -ForegroundColor Gray
} else {
Push-Location $webProject
# List all migrations
Write-Host "Listing all migrations:" -ForegroundColor Cyan
dotnet ef migrations list --project $infraProject --no-build
Pop-Location
}
Write-Host ""
# Step 4: Apply migrations
if (-not $SkipMigrations) {
Write-Host "[4/5] Applying database migrations..." -ForegroundColor Yellow
if ($WhatIf) {
Write-Host "WHAT-IF: Would run: dotnet ef database update" -ForegroundColor Gray
} else {
$confirm = Read-Host "Apply migrations to database? (y/N)"
if ($confirm -eq 'y' -or $confirm -eq 'Y') {
Push-Location $webProject
dotnet ef database update --project $infraProject --no-build
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: Migration failed" -ForegroundColor Red
Pop-Location
exit 1
}
Pop-Location
Write-Host "✓ Migrations applied successfully" -ForegroundColor Green
} else {
Write-Host "⊘ Migrations skipped by user" -ForegroundColor Yellow
}
}
} else {
Write-Host "[4/5] Skipping migrations (--SkipMigrations specified)" -ForegroundColor Gray
}
Write-Host ""
# Step 5: Summary
Write-Host "[5/5] Deployment Summary" -ForegroundColor Yellow
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Gray
Write-Host "Recent Changes Deployed:" -ForegroundColor Cyan
Write-Host " • Security Headers Added (CSP, HSTS, X-Frame-Options)" -ForegroundColor White
Write-Host " • Password Policy Strengthened (12 chars, special chars required)" -ForegroundColor White
Write-Host " • CORS Policy Restricted (config-based whitelist)" -ForegroundColor White
Write-Host " • Path Traversal Protection Enhanced" -ForegroundColor White
Write-Host " • IDOR Protection on Profile Photos" -ForegroundColor White
Write-Host " • Session Cookies Hardened (Secure, SameSite=Strict)" -ForegroundColor White
Write-Host " • JWT Expiration Reduced (15 minutes)" -ForegroundColor White
Write-Host " • File Upload Names Use GUIDs" -ForegroundColor White
Write-Host " • Input Validation (SecurityHelper class)" -ForegroundColor White
Write-Host " • AppConstants.Policies Updated (CompanyAdminOnly added)" -ForegroundColor White
Write-Host ""
Write-Host "Configuration Files:" -ForegroundColor Cyan
Write-Host " ✓ appsettings.Development.json - Dev configuration active" -ForegroundColor Green
Write-Host " ✓ appsettings.json - Production placeholders only" -ForegroundColor Green
Write-Host ""
Write-Host "Security Documentation:" -ForegroundColor Cyan
Write-Host " → SECURITY_FIXES_SUMMARY.md - Complete fix list" -ForegroundColor White
Write-Host " → DEPLOYMENT_CONFIGURATION.md - Production deployment guide" -ForegroundColor White
Write-Host ""
if (-not $WhatIf) {
Write-Host "✓ DEPLOYMENT COMPLETE" -ForegroundColor Green
} else {
Write-Host "⊘ WHAT-IF MODE - No changes made" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "Next Steps:" -ForegroundColor Cyan
Write-Host " 1. Test the application: https://localhost:58461" -ForegroundColor White
Write-Host " 2. Verify Data Lookups tab loads without CSP errors" -ForegroundColor White
Write-Host " 3. Test password policy (12 chars, special char required)" -ForegroundColor White
Write-Host " 4. Review SECURITY_FIXES_SUMMARY.md for all changes" -ForegroundColor White
Write-Host ""
Write-Host "Rollback (if needed):" -ForegroundColor Yellow
Write-Host " git log --oneline # Find commit hash before deployment" -ForegroundColor Gray
Write-Host " git reset --hard <commit-hash>" -ForegroundColor Gray
Write-Host " dotnet ef database update <migration-name> --project src/PowderCoating.Infrastructure" -ForegroundColor Gray
Write-Host ""
+60
View File
@@ -0,0 +1,60 @@
# Fix Company Admin Permissions
# Run this from the project root directory
$connectionString = "Server=.\SQLEXPRESS;Database=PowderCoatingDb;Trusted_Connection=true;MultipleActiveResultSets=true;TrustServerCertificate=true"
Write-Host "Fixing Company Admin permissions..." -ForegroundColor Yellow
$sqlQuery = @"
UPDATE AspNetUsers
SET
CanManageJobs = 1,
CanManageInventory = 1,
CanManageCustomers = 1,
CanCreateQuotes = 1,
CanApproveQuotes = 1,
CanManageCalendar = 1,
CanViewCalendar = 1,
CanManageProducts = 1,
CanViewProducts = 1,
CanManageEquipment = 1,
CanManageSuppliers = 1,
CanManageMaintenance = 1
WHERE CompanyRole = 'CompanyAdmin';
SELECT
Email,
FirstName + ' ' + LastName AS Name,
CompanyRole,
CanManageCalendar,
CanManageProducts
FROM AspNetUsers
WHERE CompanyRole = 'CompanyAdmin';
"@
try {
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = $sqlQuery
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataset) | Out-Null
Write-Host "`nCompany Admins Updated:" -ForegroundColor Green
$dataset.Tables[0] | Format-Table -AutoSize
$connection.Close()
Write-Host "`nPermissions updated successfully!" -ForegroundColor Green
Write-Host "Company Admins need to log out and log back in for changes to take effect." -ForegroundColor Yellow
}
catch {
Write-Host "Error: $_" -ForegroundColor Red
}
finally {
if ($connection.State -eq 'Open') {
$connection.Close()
}
}
+24
View File
@@ -0,0 +1,24 @@
SET NOCOUNT ON;
-- Reset password hash for superadmin@powdercoating.com / SuperAdmin123!
UPDATE AspNetUsers
SET
PasswordHash = 'AQAAAAEAAYagAAAAEOl+bhp4wG2EHbbmOqWK7xV09ve7JBpy85sH4ZPufbO36VoVDm/TwvphXES6t+DteQ==',
SecurityStamp = '3a23c797-6ae3-4d36-9bd0-5977bb51411a'
WHERE NormalizedEmail = 'SUPERADMIN@POWDERCOATING.COM';
PRINT 'Updated: superadmin@powdercoating.com';
-- Reset password hash for admin@powdercoating.com / Admin123!
UPDATE AspNetUsers
SET
PasswordHash = 'AQAAAAEAAYagAAAAEDRofgA5DOrEQ6MI/4qiifCcdqnWXBtrWhsqWDucXsB2QIvTrGaAAXQDXuZsQgnj3g==',
SecurityStamp = '27fa18d3-dd94-46fc-bfe5-bb00950d531b'
WHERE NormalizedEmail = 'ADMIN@POWDERCOATING.COM';
PRINT 'Updated: admin@powdercoating.com';
-- Verify both users exist and have non-null hashes
SELECT Email, LEN(PasswordHash) AS HashLength, IsActive
FROM AspNetUsers
WHERE NormalizedEmail IN ('SUPERADMIN@POWDERCOATING.COM', 'ADMIN@POWDERCOATING.COM');
+134
View File
@@ -0,0 +1,134 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Generates a full idempotent EF Core migration SQL script and drops it into Y:\pcc\deployment.
.DESCRIPTION
Runs `dotnet ef migrations script --idempotent` against the PowderCoating database and writes
the output to Y:\pcc\deployment\migrations_YYYYMMDD_HHmmss.sql. The script is safe to run on
any database state: EF wraps each migration in an IF NOT EXISTS check against
__EFMigrationsHistory, so already-applied migrations are skipped.
.NOTES
Run from any directory — paths are resolved relative to this script's location.
Requires: .NET 8 SDK, dotnet-ef tool (dotnet tool install --global dotnet-ef)
#>
[CmdletBinding()]
param (
[string]$OutputDir = "Y:\pcc\Deployments"
)
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$displayStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$outputFile = "migrations_$timestamp.sql"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = Split-Path -Parent $scriptDir
$webProject = Join-Path $repoRoot "src\PowderCoating.Web"
$infraProject = Join-Path $repoRoot "src\PowderCoating.Infrastructure"
$outputPath = Join-Path $OutputDir $outputFile
Write-Host ""
Write-Host "=== EF Migration Script Generator ===" -ForegroundColor Cyan
Write-Host " Web project : $webProject"
Write-Host " Output : $outputPath"
Write-Host ""
# ── Preflight checks ──────────────────────────────────────────────────────────
if (!(Test-Path $webProject)) {
Write-Host "ERROR: Web project not found at: $webProject" -ForegroundColor Red
exit 1
}
if (!(Test-Path $OutputDir)) {
Write-Host "Creating output directory: $OutputDir" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$efVersion = dotnet ef --version 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: dotnet-ef tool not found. Install with: dotnet tool install --global dotnet-ef" -ForegroundColor Red
exit 1
}
Write-Host "dotnet-ef : $($efVersion | Select-Object -First 1)" -ForegroundColor DarkGray
# ── Generate script ───────────────────────────────────────────────────────────
Write-Host "Generating idempotent migration script..." -ForegroundColor Cyan
$savedLocation = Get-Location
Set-Location $webProject
# Capture stderr to a temp file so EF's structured log noise (INF/WRN lines and
# the "Ignoring added logger provider" design-time message) don't trigger
# PowerShell 5.1's NativeCommandError handling.
$tempErr = [System.IO.Path]::GetTempFileName()
try {
# Temporarily silence $ErrorActionPreference so PS 5.1 doesn't surface EF's
# design-time stderr lines (structured log noise) as NativeCommandError objects.
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
$null = dotnet ef migrations script `
--idempotent `
--context ApplicationDbContext `
--project $infraProject `
--output $outputPath `
--no-build 2>$tempErr
$exitCode = $LASTEXITCODE
$ErrorActionPreference = $prevEAP
# Surface any genuine errors — filter out EF's routine log/diagnostic lines
if (Test-Path $tempErr) {
Get-Content $tempErr |
Where-Object {
$_ -notmatch "^\s*$" -and
$_ -notmatch "\[.*\]\s+(INF|WRN)" -and
$_ -notmatch "Ignoring added logger provider" -and
$_ -notmatch "NativeCommandError"
} |
ForEach-Object { Write-Host " $_" -ForegroundColor DarkYellow }
}
}
finally {
Set-Location $savedLocation
if (Test-Path $tempErr) { Remove-Item $tempErr -Force }
}
if ($exitCode -ne 0) {
Write-Host "ERROR: dotnet ef migrations script failed (exit code $exitCode)" -ForegroundColor Red
exit $exitCode
}
if (!(Test-Path $outputPath)) {
Write-Host "ERROR: Output file was not created: $outputPath" -ForegroundColor Red
exit 1
}
# ── Stamp header ──────────────────────────────────────────────────────────────
$header = @"
-- =============================================================================
-- Idempotent EF Core migration script
-- Project : PowderCoating.Web
-- Generated: $displayStamp
-- Safe to run on any database state (checks __EFMigrationsHistory per migration)
-- =============================================================================
"@
$existing = Get-Content $outputPath -Raw
Set-Content -Path $outputPath -Value ($header + $existing) -Encoding UTF8
# ── Report ────────────────────────────────────────────────────────────────────
$size = [math]::Round((Get-Item $outputPath).Length / 1KB, 1)
$lineCount = (Get-Content $outputPath).Count
Write-Host ""
Write-Host "Done." -ForegroundColor Green
Write-Host " File : $outputPath"
Write-Host " Size : $size KB ($lineCount lines)"
Write-Host " Stamp : $displayStamp"
Write-Host ""
+134
View File
@@ -0,0 +1,134 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Generates a full idempotent EF Core migration SQL script and drops it into Y:\pcc\deployment.
.DESCRIPTION
Runs `dotnet ef migrations script --idempotent` against the PowderCoating database and writes
the output to Y:\pcc\deployment\migrations_YYYYMMDD_HHmmss.sql. The script is safe to run on
any database state: EF wraps each migration in an IF NOT EXISTS check against
__EFMigrationsHistory, so already-applied migrations are skipped.
.NOTES
Run from any directory — paths are resolved relative to this script's location.
Requires: .NET 8 SDK, dotnet-ef tool (dotnet tool install --global dotnet-ef)
#>
[CmdletBinding()]
param (
[string]$OutputDir = "Y:\pcc\deployment"
)
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$displayStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$outputFile = "migrations_$timestamp.sql"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = Split-Path -Parent $scriptDir
$webProject = Join-Path $repoRoot "src\PowderCoating.Web"
$infraProject = Join-Path $repoRoot "src\PowderCoating.Infrastructure"
$outputPath = Join-Path $OutputDir $outputFile
Write-Host ""
Write-Host "=== EF Migration Script Generator ===" -ForegroundColor Cyan
Write-Host " Web project : $webProject"
Write-Host " Output : $outputPath"
Write-Host ""
# ── Preflight checks ──────────────────────────────────────────────────────────
if (!(Test-Path $webProject)) {
Write-Host "ERROR: Web project not found at: $webProject" -ForegroundColor Red
exit 1
}
if (!(Test-Path $OutputDir)) {
Write-Host "Creating output directory: $OutputDir" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$efVersion = dotnet ef --version 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: dotnet-ef tool not found. Install with: dotnet tool install --global dotnet-ef" -ForegroundColor Red
exit 1
}
Write-Host "dotnet-ef : $($efVersion | Select-Object -First 1)" -ForegroundColor DarkGray
# ── Generate script ───────────────────────────────────────────────────────────
Write-Host "Generating idempotent migration script..." -ForegroundColor Cyan
$savedLocation = Get-Location
Set-Location $webProject
# Capture stderr to a temp file so EF's structured log noise (INF/WRN lines and
# the "Ignoring added logger provider" design-time message) don't trigger
# PowerShell 5.1's NativeCommandError handling.
$tempErr = [System.IO.Path]::GetTempFileName()
try {
# Temporarily silence $ErrorActionPreference so PS 5.1 doesn't surface EF's
# design-time stderr lines (structured log noise) as NativeCommandError objects.
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
$null = dotnet ef migrations script `
--idempotent `
--context ApplicationDbContext `
--project $infraProject `
--output $outputPath `
--no-build 2>$tempErr
$exitCode = $LASTEXITCODE
$ErrorActionPreference = $prevEAP
# Surface any genuine errors — filter out EF's routine log/diagnostic lines
if (Test-Path $tempErr) {
Get-Content $tempErr |
Where-Object {
$_ -notmatch "^\s*$" -and
$_ -notmatch "\[.*\]\s+(INF|WRN)" -and
$_ -notmatch "Ignoring added logger provider" -and
$_ -notmatch "NativeCommandError"
} |
ForEach-Object { Write-Host " $_" -ForegroundColor DarkYellow }
}
}
finally {
Set-Location $savedLocation
if (Test-Path $tempErr) { Remove-Item $tempErr -Force }
}
if ($exitCode -ne 0) {
Write-Host "ERROR: dotnet ef migrations script failed (exit code $exitCode)" -ForegroundColor Red
exit $exitCode
}
if (!(Test-Path $outputPath)) {
Write-Host "ERROR: Output file was not created: $outputPath" -ForegroundColor Red
exit 1
}
# ── Stamp header ──────────────────────────────────────────────────────────────
$header = @"
-- =============================================================================
-- Idempotent EF Core migration script
-- Project : PowderCoating.Web
-- Generated: $displayStamp
-- Safe to run on any database state (checks __EFMigrationsHistory per migration)
-- =============================================================================
"@
$existing = Get-Content $outputPath -Raw
Set-Content -Path $outputPath -Value ($header + $existing) -Encoding UTF8
# ── Report ────────────────────────────────────────────────────────────────────
$size = [math]::Round((Get-Item $outputPath).Length / 1KB, 1)
$lineCount = (Get-Content $outputPath).Count
Write-Host ""
Write-Host "Done." -ForegroundColor Green
Write-Host " File : $outputPath"
Write-Host " Size : $size KB ($lineCount lines)"
Write-Host " Stamp : $displayStamp"
Write-Host ""
+180
View File
@@ -0,0 +1,180 @@
-- =============================================================================
-- PowderCoating App - Initial Seed SQL
-- Generated: 2026-02-28 04:10:44 UTC
--
-- Run this against your Azure SQL database when startup seeding fails.
-- All inserts are guarded with IF NOT EXISTS and are safe to re-run.
-- =============================================================================
SET NOCOUNT ON;
GO
-- 1. Default Company
IF NOT EXISTS (SELECT 1 FROM Companies WHERE CompanyCode = 'DEMO')
BEGIN
SET IDENTITY_INSERT Companies ON;
INSERT INTO Companies (
Id, CompanyId, CompanyName, CompanyCode,
PrimaryContactName, PrimaryContactEmail, Phone,
Address, City, State, ZipCode,
IsActive, SubscriptionPlan, SubscriptionStatus,
SubscriptionStartDate, TimeZone,
CreatedAt, IsDeleted
) VALUES (
1, 1, 'Demo Company', 'DEMO',
'Admin User', 'admin@demo.com', '(555) 123-4567',
'123 Demo Street', 'Demo City', 'CA', '90210',
1, 2, 0,
'2026-02-28 04:10:44', 'America/New_York',
'2026-02-28 04:10:44', 0
);
SET IDENTITY_INSERT Companies OFF;
PRINT 'Company inserted.';
END
ELSE
PRINT 'Company already exists - skipped.';
GO
-- 2. Roles
IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'SUPERADMIN')
INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)
VALUES ('208a163b-c303-4083-856e-4060bc5a923d', 'SuperAdmin', 'SUPERADMIN', NEWID());
IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'ADMINISTRATOR')
INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)
VALUES ('bf92ab6a-0d14-42d5-b19d-b5f1a5da8df0', 'Administrator', 'ADMINISTRATOR', NEWID());
IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'MANAGER')
INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)
VALUES ('c12dcd9d-408a-4d38-ba55-ef2a3f88ccb5', 'Manager', 'MANAGER', NEWID());
IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'EMPLOYEE')
INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)
VALUES ('1ab093d9-f1fb-439e-9194-5fe76be041dc', 'Employee', 'EMPLOYEE', NEWID());
IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'SHOPFLOOR')
INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)
VALUES ('13ba213d-3118-4d93-ac5d-d0674369f409', 'ShopFloor', 'SHOPFLOOR', NEWID());
IF NOT EXISTS (SELECT 1 FROM AspNetRoles WHERE NormalizedName = 'READONLY')
INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp)
VALUES ('1181bde5-0034-439b-8351-c7821faf6c28', 'ReadOnly', 'READONLY', NEWID());
PRINT 'Roles inserted.';
GO
-- 3. SuperAdmin Users
-- User 1: superadmin@powdercoating.com / SuperAdmin123!
IF NOT EXISTS (SELECT 1 FROM AspNetUsers WHERE NormalizedEmail = 'SUPERADMIN@POWDERCOATING.COM')
BEGIN
INSERT INTO AspNetUsers (
Id, UserName, NormalizedUserName, Email, NormalizedEmail,
EmailConfirmed, PasswordHash, SecurityStamp, ConcurrencyStamp,
PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled,
LockoutEnd, LockoutEnabled, AccessFailedCount,
CompanyId, CompanyRole,
FirstName, LastName, EmployeeNumber,
HireDate, IsActive,
Department, Position, HourlyRate,
Theme, DateFormat, TimeZone, SidebarColor,
CanViewShopFloor, CanManageJobs, CanManageInventory, CanManageCustomers,
CanCreateQuotes, CanApproveQuotes, CanManageCalendar, CanViewCalendar,
CanManageProducts, CanViewProducts, CanManageEquipment,
CanManageSuppliers, CanManageMaintenance,
CreatedAt
) VALUES (
'd0a94944-60ab-49dd-aff5-3f9ae6a351bc',
'superadmin@powdercoating.com', 'SUPERADMIN@POWDERCOATING.COM',
'superadmin@powdercoating.com', 'SUPERADMIN@POWDERCOATING.COM',
1, 'AQAAAAEAAYagAAAAEFSYOADLy+Inki1mJqCFcIUyEsv584NI8kyE5rorPABmuphaRajTEFbXK/VoCGQrJA==', '3b604497-51cd-401f-92cd-eda6c7c60ff2', '7f532e9b-89ee-4a6c-ab12-ab16b69f45a9',
NULL, 0, 0, NULL, 1, 0,
1, NULL,
'Super', 'Admin', 'SA-001',
'2026-02-28 04:10:44', 1,
'Platform', 'Super Administrator', 0,
'light', 'MM/dd/yyyy', 'America/New_York', 'ocean',
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1,
1, 1,
'2026-02-28 04:10:44'
);
PRINT 'User superadmin@powdercoating.com inserted.';
END
ELSE
PRINT 'User superadmin@powdercoating.com already exists - skipped.';
GO
-- User 2: admin@powdercoating.com / Admin123!
IF NOT EXISTS (SELECT 1 FROM AspNetUsers WHERE NormalizedEmail = 'ADMIN@POWDERCOATING.COM')
BEGIN
INSERT INTO AspNetUsers (
Id, UserName, NormalizedUserName, Email, NormalizedEmail,
EmailConfirmed, PasswordHash, SecurityStamp, ConcurrencyStamp,
PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled,
LockoutEnd, LockoutEnabled, AccessFailedCount,
CompanyId, CompanyRole,
FirstName, LastName, EmployeeNumber,
HireDate, IsActive,
Department, Position, HourlyRate,
Theme, DateFormat, TimeZone, SidebarColor,
CanViewShopFloor, CanManageJobs, CanManageInventory, CanManageCustomers,
CanCreateQuotes, CanApproveQuotes, CanManageCalendar, CanViewCalendar,
CanManageProducts, CanViewProducts, CanManageEquipment,
CanManageSuppliers, CanManageMaintenance,
CreatedAt
) VALUES (
'6b0f44fe-8547-41ac-81b7-6451a49077c3',
'admin@powdercoating.com', 'ADMIN@POWDERCOATING.COM',
'admin@powdercoating.com', 'ADMIN@POWDERCOATING.COM',
1, 'AQAAAAEAAYagAAAAENnCYaYw3wsQ5ZW8u7tHBs6eEtAcFKJ+35B93yDq0TMiP524/UcWO8qEoTcfyiAJnw==', '7d2b9944-345a-4819-af02-2f7f56e28120', 'b4b820d5-3168-465a-b107-2a0e99e3eab1',
NULL, 0, 0, NULL, 1, 0,
1, NULL,
'Admin', 'User', 'SA-002',
'2026-02-28 04:10:44', 1,
'Platform', 'Platform Administrator', 0,
'light', 'MM/dd/yyyy', 'America/New_York', 'ocean',
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1,
1, 1,
'2026-02-28 04:10:44'
);
PRINT 'User admin@powdercoating.com inserted.';
END
ELSE
PRINT 'User admin@powdercoating.com already exists - skipped.';
GO
-- 4. Assign SuperAdmin role to both users
DECLARE @roleId NVARCHAR(450) = (SELECT Id FROM AspNetRoles WHERE NormalizedName = 'SUPERADMIN');
DECLARE @u1Id NVARCHAR(450) = (SELECT Id FROM AspNetUsers WHERE NormalizedEmail = 'SUPERADMIN@POWDERCOATING.COM');
DECLARE @u2Id NVARCHAR(450) = (SELECT Id FROM AspNetUsers WHERE NormalizedEmail = 'ADMIN@POWDERCOATING.COM');
IF @u1Id IS NOT NULL AND @roleId IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM AspNetUserRoles WHERE UserId = @u1Id AND RoleId = @roleId)
BEGIN
INSERT INTO AspNetUserRoles (UserId, RoleId) VALUES (@u1Id, @roleId);
PRINT 'Role assigned to superadmin@powdercoating.com.';
END
IF @u2Id IS NOT NULL AND @roleId IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM AspNetUserRoles WHERE UserId = @u2Id AND RoleId = @roleId)
BEGIN
INSERT INTO AspNetUserRoles (UserId, RoleId) VALUES (@u2Id, @roleId);
PRINT 'Role assigned to admin@powdercoating.com.';
END
GO
PRINT '================================================';
PRINT 'Seed complete.';
PRINT 'Login 1: superadmin@powdercoating.com / SuperAdmin123!';
PRINT 'Login 2: admin@powdercoating.com / Admin123!';
PRINT '================================================';
GO