Demo data realism + invoice resend via SMS on any status
Seed data fixes: - Fix EF interceptor: no longer overwrites explicitly-set CreatedAt on Added entities — root cause of all "same month" chart issues - Customer seeder: generates 15 customers/month from Jan → current month; keeps 10 commercial anchors in deterministic order for job seeder index map - Invoice seeder: historical range bumped from 2→8 paid invoices/month so P&L shows consistent profit (~$5,200 collected vs ~$4,200 monthly expenses) - Month -1 bumped to 7 paid invoices to stay above expenses - Jobs: set UpdatedAt to historical event date so analytics don't need null fallback - Analytics (ReportsController): use CompletedDate ?? UpdatedAt ?? CreatedAt for revenue chart grouping; fixes empty Revenue Trend charts on Overview/Revenue tabs - SeedDataService: inject IAccountBalanceService; auto-recalculate account balances after seeding; patch checking/savings opening balances unconditionally on reset - Customer list: sort by CompanyName ?? ContactLastName so individuals and commercial accounts interleave instead of appearing as two blocks Invoice resend: - ResendInvoice action now accepts sendEmail + sendSms parameters; SMS-only resend no longer requires an email address on file - Ensures PublicViewToken exists before SMS so the view link is always valid - canResend in Details view now allows Paid invoices (removed != Paid guard) - Resend button shows channel-choice modal when customer has both email + SMS, direct SMS button when SMS only, or email button when email only - New #resendChannelModal mirrors the Send channel modal but posts to ResendInvoice - resendInvoice() JS updated to pass sendEmail/sendSms query params Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -146,12 +146,19 @@ public class ReportsController : Controller
|
||||
var momGrowth = revenueLastMonth > 0 ? Math.Round((revenueThisMonth - revenueLastMonth) / revenueLastMonth * 100, 1) : 0m;
|
||||
|
||||
// === REVENUE ANALYTICS ===
|
||||
// Pre-filter completed jobs by date range once for monthly calculations
|
||||
var completedJobsInRange = completedJobs.Where(j => j.UpdatedAt >= startDate).ToList();
|
||||
// CompletedDate is the authoritative "when the job finished" date.
|
||||
// UpdatedAt is set by the EF interceptor only on Modified saves, so seeded/imported
|
||||
// jobs may have UpdatedAt = null. Fall back to CreatedAt as a last resort.
|
||||
static DateTime JobMonthDate(Job j) =>
|
||||
j.CompletedDate ?? j.UpdatedAt ?? j.CreatedAt;
|
||||
|
||||
var completedJobsInRange = completedJobs
|
||||
.Where(j => JobMonthDate(j) >= startDate)
|
||||
.ToList();
|
||||
|
||||
// Group by month for efficient monthly aggregations
|
||||
var jobsByMonth = completedJobsInRange
|
||||
.GroupBy(j => new DateTime(j.UpdatedAt.Value.Year, j.UpdatedAt.Value.Month, 1))
|
||||
.GroupBy(j => { var d = JobMonthDate(j); return new DateTime(d.Year, d.Month, 1); })
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
// Monthly revenue trend
|
||||
|
||||
Reference in New Issue
Block a user