82fb48f7a5
- DataExportController + AccountDataExportController: add ProjectName to Jobs, Quotes, Invoices (XLSX + CSV); add LeadSource + ShipTo fields to Customers (XLSX + CSV); add CustomerContacts sheet/CSV (new) - Both export views: add Customer Contacts checkbox (checked by default) - CustomerImportDto: add LeadSource + ShipTo* fields - JobImportDto: add ProjectName - QuoteImportDto: add ProjectName - InvoiceImportDto: add Project Name (dual-name alias for round-trip) - CsvImportService: wire all new import fields to entity creation; also patch invoice update path for ProjectName - Add scripts/purge_imported_data.sql (dry-run T-SQL for data cleanup) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
404 lines
16 KiB
Transact-SQL
404 lines
16 KiB
Transact-SQL
-- =============================================================================
|
|
-- 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
|