-- Safe Multi-Tenancy Migration Script -- Run this instead of 'dotnet ef database update' USE PowderCoatingDb; GO PRINT '=== Step 1: Create Companies Table ==='; IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Companies') BEGIN CREATE TABLE Companies ( Id INT IDENTITY(1,1) PRIMARY KEY, CompanyName NVARCHAR(MAX) NOT NULL, CompanyCode NVARCHAR(450) NULL, PrimaryContactName NVARCHAR(MAX) NOT NULL, PrimaryContactEmail NVARCHAR(MAX) NOT NULL, Phone NVARCHAR(MAX) NULL, Address NVARCHAR(MAX) NULL, City NVARCHAR(MAX) NULL, State NVARCHAR(MAX) NULL, ZipCode NVARCHAR(MAX) NULL, IsActive BIT NOT NULL DEFAULT 1, SubscriptionStartDate DATETIME2 NOT NULL, SubscriptionEndDate DATETIME2 NULL, SubscriptionPlan NVARCHAR(MAX) NULL, TimeZone NVARCHAR(MAX) NULL, LogoPath NVARCHAR(MAX) NULL, Settings NVARCHAR(MAX) NULL, CompanyId INT NOT NULL DEFAULT 0, CreatedAt DATETIME2 NOT NULL, UpdatedAt DATETIME2 NULL, CreatedBy NVARCHAR(MAX) NULL, UpdatedBy NVARCHAR(MAX) NULL, IsDeleted BIT NOT NULL DEFAULT 0, DeletedAt DATETIME2 NULL, DeletedBy NVARCHAR(MAX) NULL ); CREATE UNIQUE INDEX IX_Companies_CompanyCode ON Companies(CompanyCode) WHERE CompanyCode IS NOT NULL; PRINT 'Companies table created'; END GO PRINT '=== Step 2: Insert Default Company ==='; IF NOT EXISTS (SELECT * FROM Companies WHERE Id = 1) BEGIN SET IDENTITY_INSERT Companies ON; INSERT INTO Companies ( Id, CompanyName, CompanyCode, PrimaryContactName, PrimaryContactEmail, Phone, Address, City, State, ZipCode, IsActive, SubscriptionStartDate, SubscriptionPlan, TimeZone, CompanyId, CreatedAt, IsDeleted ) VALUES ( 1, 'Demo Company', 'DEMO', 'Admin User', 'admin@demo.com', '(555) 123-4567', '123 Demo Street', 'Demo City', 'CA', '90210', 1, GETUTCDATE(), 'Enterprise', 'America/New_York', 1, GETUTCDATE(), 0 ); SET IDENTITY_INSERT Companies OFF; PRINT 'Default company inserted'; END GO PRINT '=== Step 3: Add CompanyId Columns ==='; -- AspNetUsers IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('AspNetUsers') AND name = 'CompanyId') BEGIN ALTER TABLE AspNetUsers ADD CompanyId INT NOT NULL DEFAULT 1; PRINT 'Added CompanyId to AspNetUsers'; END IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('AspNetUsers') AND name = 'CompanyRole') BEGIN ALTER TABLE AspNetUsers ADD CompanyRole NVARCHAR(MAX) NULL; PRINT 'Added CompanyRole to AspNetUsers'; END -- Other tables DECLARE @sql NVARCHAR(MAX); DECLARE @tableName NVARCHAR(128); DECLARE cur CURSOR FOR SELECT name FROM sys.tables WHERE name IN ('Customers', 'Jobs', 'JobItems', 'Quotes', 'QuoteItems', 'InventoryItems', 'InventoryTransactions', 'Equipment', 'MaintenanceRecords', 'Suppliers', 'PricingTiers', 'JobPhotos', 'JobNotes', 'CustomerNotes', 'JobStatusHistory'); OPEN cur; FETCH NEXT FROM cur INTO @tableName; WHILE @@FETCH_STATUS = 0 BEGIN IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(@tableName) AND name = 'CompanyId') BEGIN SET @sql = N'ALTER TABLE ' + QUOTENAME(@tableName) + N' ADD CompanyId INT NOT NULL DEFAULT 1'; EXEC sp_executesql @sql; PRINT 'Added CompanyId to ' + @tableName; END FETCH NEXT FROM cur INTO @tableName; END CLOSE cur; DEALLOCATE cur; GO PRINT '=== Step 4: Create Indexes ==='; DECLARE @indexSql NVARCHAR(MAX); DECLARE @tbl NVARCHAR(128); DECLARE @idxName NVARCHAR(256); DECLARE idxCur CURSOR FOR SELECT name FROM sys.tables WHERE name IN ('Customers', 'Jobs', 'Equipment', 'Quotes', 'InventoryItems', 'Suppliers', 'PricingTiers'); OPEN idxCur; FETCH NEXT FROM idxCur INTO @tbl; WHILE @@FETCH_STATUS = 0 BEGIN SET @idxName = 'IX_' + @tbl + '_CompanyId'; IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = @idxName) BEGIN SET @indexSql = N'CREATE INDEX ' + QUOTENAME(@idxName) + N' ON ' + QUOTENAME(@tbl) + N'(CompanyId)'; EXEC sp_executesql @indexSql; PRINT 'Created index ' + @idxName; END FETCH NEXT FROM idxCur INTO @tbl; END CLOSE idxCur; DEALLOCATE idxCur; GO PRINT '=== Step 5: Add Foreign Keys ==='; -- AspNetUsers -> Companies IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE name = 'FK_AspNetUsers_Companies_CompanyId') BEGIN ALTER TABLE AspNetUsers ADD CONSTRAINT FK_AspNetUsers_Companies_CompanyId FOREIGN KEY (CompanyId) REFERENCES Companies(Id); PRINT 'Added FK: AspNetUsers -> Companies'; END -- All other tables -> Companies DECLARE @fkSql NVARCHAR(MAX); DECLARE @table NVARCHAR(128); DECLARE @fkName NVARCHAR(256); DECLARE fkCur CURSOR FOR SELECT name FROM sys.tables WHERE name IN ('Customers', 'Jobs', 'Equipment', 'Quotes', 'InventoryItems', 'Suppliers', 'PricingTiers'); OPEN fkCur; FETCH NEXT FROM fkCur INTO @table; WHILE @@FETCH_STATUS = 0 BEGIN SET @fkName = 'FK_' + @table + '_Companies_CompanyId'; IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE name = @fkName) BEGIN SET @fkSql = N'ALTER TABLE ' + QUOTENAME(@table) + N' ADD CONSTRAINT ' + QUOTENAME(@fkName) + N' FOREIGN KEY (CompanyId) REFERENCES Companies(Id)'; EXEC sp_executesql @fkSql; PRINT 'Added FK: ' + @table + ' -> Companies'; END FETCH NEXT FROM fkCur INTO @table; END CLOSE fkCur; DEALLOCATE fkCur; GO PRINT '=== Step 6: Update Migration History ==='; IF NOT EXISTS (SELECT * FROM __EFMigrationsHistory WHERE MigrationId = '20260206004522_AddMultiTenancyFixed') BEGIN INSERT INTO __EFMigrationsHistory (MigrationId, ProductVersion) VALUES ('20260206004522_AddMultiTenancyFixed', '8.0.0'); PRINT 'Migration history updated'; END GO PRINT ''; PRINT '=============================================='; PRINT 'Multi-Tenancy Migration Completed Successfully!'; PRINT '=============================================='; PRINT 'Next steps:'; PRINT '1. Run the application: dotnet run --project src/PowderCoating.Web'; PRINT '2. Login with: superadmin@powdercoating.com / SuperAdmin123!'; PRINT '=============================================='; GO