-- ============================================================================= -- Company Data Purge Script -- Removes financial, job, and quote data created before a cutoff date. -- Customers and Vendors are always preserved. -- -- WHAT THIS DELETES (for records where CreatedAt < @CutoffDate): -- • Journal entries & bank reconciliations -- • Bills, bill payments, purchase orders, vendor credits, expenses -- • Invoices, invoice payments, credit memos, refunds, deposits -- • Jobs and all child records (items, coats, prep, notes, photos, etc.) -- • Quotes and all child records -- -- WHAT THIS KEEPS (always): -- • Customers, customer notes, customer contacts, preferred powders -- • Vendors -- • Inventory items and inventory transactions -- • Equipment, catalog items, pricing tiers -- • Company settings and configuration -- • Any record with CreatedAt >= @CutoffDate -- -- INSTRUCTIONS: -- 1. Set @CompanyId — find it with: SELECT Id, Name FROM Companies -- 2. Set @CutoffDate — records created BEFORE this date are deleted -- 3. Run with @DryRun = 1 first and review the row counts printed -- 4. Back up the database before setting @DryRun = 0 -- 5. Set @DryRun = 0 and run again to apply -- ============================================================================= DECLARE @CutoffDate DATE = '2026-01-01'; -- Delete records created BEFORE this date DECLARE @CompanyId INT = 0; -- !! Set to your company ID before running DECLARE @DryRun BIT = 1; -- 1 = preview counts only | 0 = apply deletes -- ============================================================================= SET NOCOUNT ON; IF @CompanyId = 0 BEGIN RAISERROR('ERROR: Set @CompanyId before running this script. Run: SELECT Id, Name FROM Companies', 16, 1); RETURN; END PRINT '============================================================'; PRINT 'Purge run at: ' + CONVERT(NVARCHAR, GETDATE(), 120); PRINT 'Company ID : ' + CAST(@CompanyId AS NVARCHAR); PRINT 'Cutoff date : ' + CAST(@CutoffDate AS NVARCHAR) + ' (records BEFORE this date)'; PRINT 'Dry run : ' + CASE @DryRun WHEN 1 THEN 'YES — no changes will be made' ELSE 'NO — deletes will be applied' END; PRINT '============================================================'; BEGIN TRANSACTION; -- =========================================================================== -- SECTION 1 — JOURNAL ENTRIES & GL -- =========================================================================== PRINT ''; PRINT '--- Section 1: Journal Entries & GL ---'; -- Null the self-referential ReversalOfId FK before deleting UPDATE JournalEntries SET ReversalOfId = NULL WHERE CompanyId = @CompanyId AND ReversalOfId IN (SELECT Id FROM JournalEntries WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); DELETE FROM JournalEntryLines WHERE JournalEntryId IN ( SELECT Id FROM JournalEntries WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JournalEntryLines deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JournalEntries WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'JournalEntries deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM BankReconciliations WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'BankReconciliations deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); -- =========================================================================== -- SECTION 2 — BILLS, PURCHASE ORDERS & EXPENSES -- =========================================================================== PRINT ''; PRINT '--- Section 2: Bills, Purchase Orders & Expenses ---'; -- Vendor credits (must come before Bills because VendorCreditApplications references both) DELETE FROM VendorCreditApplications WHERE VendorCreditId IN ( SELECT Id FROM VendorCredits WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'VendorCreditApplications deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM VendorCreditLineItems WHERE VendorCreditId IN ( SELECT Id FROM VendorCredits WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'VendorCreditLineItems deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM VendorCredits WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'VendorCredits deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Bills DELETE FROM BillPayments WHERE BillId IN ( SELECT Id FROM Bills WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'BillPayments deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM BillLineItems WHERE BillId IN ( SELECT Id FROM Bills WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'BillLineItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM Bills WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'Bills deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Purchase orders DELETE FROM PurchaseOrderItems WHERE PurchaseOrderId IN ( SELECT Id FROM PurchaseOrders WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'PurchaseOrderItems deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM PurchaseOrders WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'PurchaseOrders deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Expenses DELETE FROM Expenses WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'Expenses deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- =========================================================================== -- SECTION 3 — INVOICES, PAYMENTS & DEPOSITS -- =========================================================================== PRINT ''; PRINT '--- Section 3: Invoices, Payments & Deposits ---'; -- CreditMemos: NULL the OriginalInvoiceId FK before deleting the invoice it points to UPDATE CreditMemos SET OriginalInvoiceId = NULL WHERE CompanyId = @CompanyId AND OriginalInvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); DELETE FROM CreditMemoApplications WHERE InvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate) OR CreditMemoId IN ( SELECT Id FROM CreditMemos WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'CreditMemoApplications deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM CreditMemos WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'CreditMemos deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Refunds and gift-cert redemptions tied to deleted invoices DELETE FROM Refunds WHERE InvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'Refunds deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM GiftCertificateRedemptions WHERE InvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'GiftCertificateRedemptions deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Payments DELETE FROM Payments WHERE InvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'Payments deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- InvoiceItems (NULL SourceJobItemId on any invoice items that survive but point at deleted jobs) UPDATE InvoiceItems SET SourceJobItemId = NULL WHERE SourceJobItemId IN ( SELECT ji.Id FROM JobItems ji INNER JOIN Jobs j ON ji.JobId = j.Id WHERE j.CompanyId = @CompanyId AND j.CreatedAt < @CutoffDate); DELETE FROM InvoiceItems WHERE InvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'InvoiceItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Deposits: clear the AppliedToInvoiceId FK before deleting the invoices UPDATE Deposits SET AppliedToInvoiceId = NULL, AppliedDate = NULL WHERE CompanyId = @CompanyId AND AppliedToInvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); -- Now delete deposits that were created before the cutoff DELETE FROM Deposits WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'Deposits deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Notification logs referencing deleted invoices UPDATE NotificationLogs SET InvoiceId = NULL WHERE CompanyId = @CompanyId AND InvoiceId IN ( SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); DELETE FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'Invoices deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- =========================================================================== -- SECTION 4 — JOBS -- =========================================================================== PRINT ''; PRINT '--- Section 4: Jobs ---'; -- NULL FKs in other tables that point to jobs/job-items being deleted -- -- BillLineItems.JobId (bill survived but referenced a deleted job) UPDATE BillLineItems SET JobId = NULL WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); -- Expenses.JobId UPDATE Expenses SET JobId = NULL WHERE CompanyId = @CompanyId AND JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); -- Appointments.JobId UPDATE Appointments SET JobId = NULL WHERE CompanyId = @CompanyId AND JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); -- Deposits.JobId (NoAction FK — must NULL before deleting job) UPDATE Deposits SET JobId = NULL WHERE CompanyId = @CompanyId AND JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); -- NotificationLogs.JobId UPDATE NotificationLogs SET JobId = NULL WHERE CompanyId = @CompanyId AND JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); -- OvenBatchItems: delete before OvenBatches and before JobItems DELETE FROM OvenBatchItems WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'OvenBatchItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Clean up now-empty OvenBatches (batches belonging to this company with no remaining items) DELETE FROM OvenBatches WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate AND Id NOT IN (SELECT DISTINCT OvenBatchId FROM OvenBatchItems); PRINT 'OvenBatches deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- ReworkRecords (JobId required FK; ReworkJobId optional) DELETE FROM ReworkRecords WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate) OR ReworkJobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'ReworkRecords deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- JobItem children (coats and prep services) DELETE FROM JobItemCoats WHERE JobItemId IN ( SELECT Id FROM JobItems WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate)); PRINT 'JobItemCoats deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JobItemPrepServices WHERE JobItemId IN ( SELECT Id FROM JobItems WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate)); PRINT 'JobItemPrepServices deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JobItems WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JobItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- Job metadata tables DELETE FROM JobChangeHistories WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JobChangeHistories deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JobTimeEntries WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JobTimeEntries deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JobPhotos WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JobPhotos deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JobNotes WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JobNotes deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JobStatusHistory WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JobStatusHistory deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM JobDailyPriorities WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'JobDailyPriorities deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM PowderUsageLogs WHERE JobId IN ( SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'PowderUsageLogs deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM AiItemPredictions WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'AiItemPredictions deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'Jobs deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- =========================================================================== -- SECTION 5 — QUOTES -- =========================================================================== PRINT ''; PRINT '--- Section 5: Quotes ---'; -- NULL FKs that point to quotes being deleted UPDATE Deposits SET QuoteId = NULL WHERE CompanyId = @CompanyId AND QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); UPDATE NotificationLogs SET QuoteId = NULL WHERE CompanyId = @CompanyId AND QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); -- QuoteItem children DELETE FROM QuoteItemCoats WHERE QuoteItemId IN ( SELECT Id FROM QuoteItems WHERE QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate)); PRINT 'QuoteItemCoats deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM QuoteItemPrepServices WHERE QuoteItemId IN ( SELECT Id FROM QuoteItems WHERE QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate)); PRINT 'QuoteItemPrepServices deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM QuoteItems WHERE QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'QuoteItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM QuoteChangeHistories WHERE QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'QuoteChangeHistories deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM QuotePhotos WHERE QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'QuotePhotos deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- QuotePrepServices are scoped to the quote's company via the quote FK, not directly DELETE FROM QuotePrepServices WHERE QuoteId IN ( SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate); PRINT 'QuotePrepServices deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); DELETE FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate; PRINT 'Quotes deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR); -- =========================================================================== -- SUMMARY & COMMIT / ROLLBACK -- =========================================================================== PRINT ''; PRINT '============================================================'; IF @DryRun = 1 BEGIN PRINT 'DRY RUN complete — rolling back. No data was changed.'; PRINT 'Set @DryRun = 0 and run again to apply the deletes.'; ROLLBACK TRANSACTION; END ELSE BEGIN PRINT 'Purge complete — committing.'; COMMIT TRANSACTION; END