Fix Company Settings save, invoice PAID stamp, and purge script

- Company Settings: switch save button from type=submit to type=button
  to bypass HTML5 form validation blocking the submit event; replace
  AutoMapper Map() with explicit property assignment so EF change
  tracking reliably detects mutations; fix showButtonSuccess() never
  re-enabling the button after a successful save
- Invoice PDF: move PAID stamp into the header row as a centered middle
  column so it sits between the company and invoice blocks without
  adding height to the document
- Purge script: use business-date fields instead of CreatedAt so
  imported records (which all share today's CreatedAt) are correctly
  filtered by actual transaction dates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 17:36:15 -04:00
parent 66c3febd7a
commit 0b839d0746
4 changed files with 122 additions and 90 deletions
+98 -70
View File
@@ -1,14 +1,16 @@
-- =============================================================================
-- Company Data Purge Script
-- Removes financial, job, and quote data created before a cutoff date.
-- Removes financial, job, and quote data dated 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 DELETES (using each entity's own business date, not CreatedAt):
-- • Journal entries (EntryDate) & bank reconciliations (StatementDate)
-- • Bills (BillDate), bill payments (PaymentDate), purchase orders (OrderDate)
-- • Vendor credits (CreditDate), expenses (Date)
-- • Invoices (InvoiceDate), payments (PaymentDate), deposits (ReceivedDate)
-- • Credit memos, refunds, gift cert redemptions tied to deleted invoices
-- • Jobs (IntakeDate, falling back to CreatedAt when null) and all child records
-- • Quotes (QuoteDate) and all child records
--
-- WHAT THIS KEEPS (always):
-- • Customers, customer notes, customer contacts, preferred powders
@@ -16,17 +18,17 @@
-- • Inventory items and inventory transactions
-- • Equipment, catalog items, pricing tiers
-- • Company settings and configuration
-- • Any record with CreatedAt >= @CutoffDate
-- • Any record whose business date >= @CutoffDate
--
-- INSTRUCTIONS:
-- 1. Set @CompanyId — find it with: SELECT Id, Name FROM Companies
-- 2. Set @CutoffDate — records created BEFORE this date are deleted
-- 2. Set @CutoffDate — records dated 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 @CutoffDate DATE = '2026-01-01'; -- Delete records dated 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
@@ -50,6 +52,7 @@ BEGIN TRANSACTION;
-- ===========================================================================
-- SECTION 1 — JOURNAL ENTRIES & GL
-- Uses EntryDate (JournalEntries) and StatementDate (BankReconciliations)
-- ===========================================================================
PRINT '';
PRINT '--- Section 1: Journal Entries & GL ---';
@@ -58,23 +61,24 @@ PRINT '--- Section 1: Journal Entries & GL ---';
UPDATE JournalEntries
SET ReversalOfId = NULL
WHERE CompanyId = @CompanyId
AND ReversalOfId IN (SELECT Id FROM JournalEntries WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
AND ReversalOfId IN (SELECT Id FROM JournalEntries WHERE CompanyId = @CompanyId AND CAST(EntryDate AS DATE) < @CutoffDate);
DELETE FROM JournalEntryLines
WHERE JournalEntryId IN (
SELECT Id FROM JournalEntries WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM JournalEntries WHERE CompanyId = @CompanyId AND CAST(EntryDate AS DATE) < @CutoffDate);
PRINT 'JournalEntryLines deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM JournalEntries
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(EntryDate AS DATE) < @CutoffDate;
PRINT 'JournalEntries deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM BankReconciliations
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(StatementDate AS DATE) < @CutoffDate;
PRINT 'BankReconciliations deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- ===========================================================================
-- SECTION 2 — BILLS, PURCHASE ORDERS & EXPENSES
-- Uses CreditDate (VendorCredits), BillDate (Bills), OrderDate (POs), Date (Expenses)
-- ===========================================================================
PRINT '';
PRINT '--- Section 2: Bills, Purchase Orders & Expenses ---';
@@ -82,50 +86,53 @@ 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);
SELECT Id FROM VendorCredits WHERE CompanyId = @CompanyId AND CAST(CreditDate AS DATE) < @CutoffDate);
PRINT 'VendorCreditApplications deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM VendorCreditLineItems
WHERE VendorCreditId IN (
SELECT Id FROM VendorCredits WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM VendorCredits WHERE CompanyId = @CompanyId AND CAST(CreditDate AS DATE) < @CutoffDate);
PRINT 'VendorCreditLineItems deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM VendorCredits
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(CreditDate AS DATE) < @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);
SELECT Id FROM Bills WHERE CompanyId = @CompanyId AND CAST(BillDate AS DATE) < @CutoffDate);
PRINT 'BillPayments deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM BillLineItems
WHERE BillId IN (
SELECT Id FROM Bills WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Bills WHERE CompanyId = @CompanyId AND CAST(BillDate AS DATE) < @CutoffDate);
PRINT 'BillLineItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM Bills
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(BillDate AS DATE) < @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);
SELECT Id FROM PurchaseOrders WHERE CompanyId = @CompanyId AND CAST(OrderDate AS DATE) < @CutoffDate);
PRINT 'PurchaseOrderItems deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM PurchaseOrders
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(OrderDate AS DATE) < @CutoffDate;
PRINT 'PurchaseOrders deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- Expenses
-- Expenses (Date column)
DELETE FROM Expenses
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST([Date] AS DATE) < @CutoffDate;
PRINT 'Expenses deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- ===========================================================================
-- SECTION 3 — INVOICES, PAYMENTS & DEPOSITS
-- Uses InvoiceDate (Invoices), PaymentDate (Payments), ReceivedDate (Deposits)
-- CreditMemos/Refunds/GiftCertRedemptions have no standalone date — deleted
-- only when their parent invoice falls within the cutoff.
-- ===========================================================================
PRINT '';
PRINT '--- Section 3: Invoices, Payments & Deposits ---';
@@ -135,34 +142,34 @@ UPDATE CreditMemos
SET OriginalInvoiceId = NULL
WHERE CompanyId = @CompanyId
AND OriginalInvoiceId IN (
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate);
DELETE FROM CreditMemoApplications
WHERE InvoiceId IN (
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate)
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate)
OR CreditMemoId IN (
SELECT Id FROM CreditMemos WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM CreditMemos WHERE CompanyId = @CompanyId AND CAST(CreatedAt AS DATE) < @CutoffDate);
PRINT 'CreditMemoApplications deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM CreditMemos
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(CreatedAt AS DATE) < @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);
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate);
PRINT 'Refunds deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM GiftCertificateRedemptions
WHERE InvoiceId IN (
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate);
PRINT 'GiftCertificateRedemptions deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- Payments
-- Payments (PaymentDate)
DELETE FROM Payments
WHERE InvoiceId IN (
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate);
PRINT 'Payments deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- InvoiceItems (NULL SourceJobItemId on any invoice items that survive but point at deleted jobs)
@@ -171,11 +178,12 @@ 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);
WHERE j.CompanyId = @CompanyId
AND CAST(COALESCE(j.IntakeDate, j.CreatedAt) AS DATE) < @CutoffDate);
DELETE FROM InvoiceItems
WHERE InvoiceId IN (
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate);
PRINT 'InvoiceItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- Deposits: clear the AppliedToInvoiceId FK before deleting the invoices
@@ -184,11 +192,11 @@ SET AppliedToInvoiceId = NULL,
AppliedDate = NULL
WHERE CompanyId = @CompanyId
AND AppliedToInvoiceId IN (
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate);
-- Now delete deposits that were created before the cutoff
-- Now delete deposits that fall within the cutoff (ReceivedDate)
DELETE FROM Deposits
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(ReceivedDate AS DATE) < @CutoffDate;
PRINT 'Deposits deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- Notification logs referencing deleted invoices
@@ -196,14 +204,16 @@ UPDATE NotificationLogs
SET InvoiceId = NULL
WHERE CompanyId = @CompanyId
AND InvoiceId IN (
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Invoices WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate);
DELETE FROM Invoices
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(InvoiceDate AS DATE) < @CutoffDate;
PRINT 'Invoices deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- ===========================================================================
-- SECTION 4 — JOBS
-- Uses COALESCE(IntakeDate, CreatedAt) — IntakeDate is the business date;
-- falls back to CreatedAt when not set (e.g. jobs created before IntakeDate existed).
-- ===========================================================================
PRINT '';
PRINT '--- Section 4: Jobs ---';
@@ -214,121 +224,140 @@ PRINT '--- Section 4: Jobs ---';
UPDATE BillLineItems
SET JobId = NULL
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
-- Expenses.JobId
UPDATE Expenses
SET JobId = NULL
WHERE CompanyId = @CompanyId
AND JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
-- Appointments.JobId
UPDATE Appointments
SET JobId = NULL
WHERE CompanyId = @CompanyId
AND JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @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);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
-- NotificationLogs.JobId
UPDATE NotificationLogs
SET JobId = NULL
WHERE CompanyId = @CompanyId
AND JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
-- OvenBatchItems: delete before OvenBatches and before JobItems
DELETE FROM OvenBatchItems
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @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)
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate)
OR ReworkJobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @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));
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @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));
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate));
PRINT 'JobItemPrepServices deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM JobItems
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @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);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
PRINT 'JobChangeHistories deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM JobTimeEntries
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
PRINT 'JobTimeEntries deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM JobPhotos
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
PRINT 'JobPhotos deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM JobNotes
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
PRINT 'JobNotes deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM JobStatusHistory
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
PRINT 'JobStatusHistory deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM JobDailyPriorities
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
PRINT 'JobDailyPriorities deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM PowderUsageLogs
WHERE JobId IN (
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Jobs WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate);
PRINT 'PowderUsageLogs deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM AiItemPredictions
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(CreatedAt AS DATE) < @CutoffDate;
PRINT 'AiItemPredictions deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM Jobs
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId
AND CAST(COALESCE(IntakeDate, CreatedAt) AS DATE) < @CutoffDate;
PRINT 'Jobs deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- ===========================================================================
-- SECTION 5 — QUOTES
-- Uses QuoteDate
-- ===========================================================================
PRINT '';
PRINT '--- Section 5: Quotes ---';
@@ -338,50 +367,49 @@ UPDATE Deposits
SET QuoteId = NULL
WHERE CompanyId = @CompanyId
AND QuoteId IN (
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @CutoffDate);
UPDATE NotificationLogs
SET QuoteId = NULL
WHERE CompanyId = @CompanyId
AND QuoteId IN (
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @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));
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @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));
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @CutoffDate));
PRINT 'QuoteItemPrepServices deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM QuoteItems
WHERE QuoteId IN (
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @CutoffDate);
PRINT 'QuoteItems deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM QuoteChangeHistories
WHERE QuoteId IN (
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @CutoffDate);
PRINT 'QuoteChangeHistories deleted: ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM QuotePhotos
WHERE QuoteId IN (
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate);
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @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);
SELECT Id FROM Quotes WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @CutoffDate);
PRINT 'QuotePrepServices deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
DELETE FROM Quotes
WHERE CompanyId = @CompanyId AND CreatedAt < @CutoffDate;
WHERE CompanyId = @CompanyId AND CAST(QuoteDate AS DATE) < @CutoffDate;
PRINT 'Quotes deleted : ' + CAST(@@ROWCOUNT AS NVARCHAR);
-- ===========================================================================
@@ -147,6 +147,14 @@ public class PdfService : IPdfService
column.Item().Text(companyInfo.PrimaryContactEmail).FontSize(9).FontColor(Colors.Grey.Darken1);
});
if (invoice.Status == InvoiceStatus.Paid)
{
row.RelativeItem().AlignCenter().AlignMiddle()
.Border(2).BorderColor(Colors.Green.Darken1)
.PaddingVertical(6).PaddingHorizontal(16)
.Text("PAID").FontSize(20).Bold().FontColor(Colors.Green.Darken1).LetterSpacing(0.15f);
}
row.RelativeItem().AlignRight().Column(column =>
{
column.Item().Text("INVOICE").FontSize(28).Bold().FontColor(accentColor);
@@ -158,16 +166,6 @@ public class PdfService : IPdfService
});
});
if (invoice.Status == InvoiceStatus.Paid)
{
col.Item().PaddingVertical(6).AlignCenter().Column(badge =>
{
badge.Item().AlignCenter().Border(2).BorderColor(Colors.Green.Darken1)
.PaddingVertical(4).PaddingHorizontal(24)
.Text("PAID").FontSize(18).Bold().FontColor(Colors.Green.Darken1).LetterSpacing(0.15f);
});
}
col.Item().PaddingVertical(4).LineHorizontal(1).LineColor(accentColor);
});
}
@@ -230,11 +230,19 @@ public class CompanySettingsController : Controller
return Json(new { success = false, message = "Company not found." });
}
// Update company properties
_mapper.Map(dto, company);
company.UpdatedAt = DateTime.UtcNow;
// Explicit assignment avoids AutoMapper quirks with tracked EF entities
company.CompanyName = dto.CompanyName.Trim();
company.CompanyCode = string.IsNullOrWhiteSpace(dto.CompanyCode) ? null : dto.CompanyCode.Trim();
company.PrimaryContactName = dto.PrimaryContactName.Trim();
company.PrimaryContactEmail = dto.PrimaryContactEmail.Trim();
company.Phone = string.IsNullOrWhiteSpace(dto.Phone) ? null : dto.Phone.Trim();
company.Address = string.IsNullOrWhiteSpace(dto.Address) ? null : dto.Address.Trim();
company.City = string.IsNullOrWhiteSpace(dto.City) ? null : dto.City.Trim();
company.State = string.IsNullOrWhiteSpace(dto.State) ? null : dto.State.Trim();
company.ZipCode = string.IsNullOrWhiteSpace(dto.ZipCode) ? null : dto.ZipCode.Trim();
company.TimeZone = string.IsNullOrWhiteSpace(dto.TimeZone) ? null : dto.TimeZone.Trim();
company.AccountingMethod = dto.AccountingMethod;
await _unitOfWork.Companies.UpdateAsync(company);
await _unitOfWork.CompleteAsync();
_logger.LogInformation("Company {CompanyId} settings updated by user", companyId);
@@ -312,7 +312,7 @@
</div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary" id="btnSaveCompanyInfo">
<button type="button" class="btn btn-primary" id="btnSaveCompanyInfo">
<i class="bi bi-save"></i> Save Changes
</button>
</div>
@@ -2749,10 +2749,8 @@
}
});
// Company Info Form Submit
$('#companyInfoForm').on('submit', function (e) {
e.preventDefault();
// Company Info Save
$('#btnSaveCompanyInfo').on('click', function () {
const formData = {
CompanyName: $('#companyName').val(),
CompanyCode: $('#companyCode').val(),
@@ -3192,7 +3190,7 @@
// Button success animation helper
function showButtonSuccess(btn, originalHtml, duration = 2000) {
btn.removeClass('btn-primary').addClass('btn-success');
btn.prop('disabled', false).removeClass('btn-primary').addClass('btn-success');
btn.html('<i class="bi bi-check-circle-fill"></i> Saved!');
setTimeout(function() {
btn.removeClass('btn-success').addClass('btn-primary');