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:
@@ -115,19 +115,26 @@ public partial class SeedDataService
|
||||
|
||||
if (workers.Count == 0) return 0;
|
||||
|
||||
// Only create entries for jobs that have been worked on
|
||||
var activeJobs = await _context.Set<Job>()
|
||||
// Resolve status IDs first — avoids relying on Include(j => j.JobStatus) which can
|
||||
// silently return null navigation properties when query filters interact with IgnoreQueryFilters.
|
||||
var workedStatusIds = await _context.Set<JobStatusLookup>()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(j => j.CompanyId == company.Id && !j.IsDeleted)
|
||||
.Include(j => j.JobStatus)
|
||||
.Where(s => s.CompanyId == company.Id && new[]
|
||||
{
|
||||
"IN_PREPARATION", "SANDBLASTING", "MASKING_TAPING", "CLEANING",
|
||||
"IN_OVEN", "COATING", "CURING", "QUALITY_CHECK",
|
||||
"COMPLETED", "READY_FOR_PICKUP", "DELIVERED"
|
||||
}.Contains(s.StatusCode))
|
||||
.Select(s => s.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var workedJobs = activeJobs.Where(j =>
|
||||
j.JobStatus?.StatusCode is
|
||||
"IN_PREPARATION" or "SANDBLASTING" or "MASKING_TAPING" or "CLEANING" or
|
||||
"IN_OVEN" or "COATING" or "CURING" or "QUALITY_CHECK" or
|
||||
"COMPLETED" or "READY_FOR_PICKUP" or "DELIVERED"
|
||||
).ToList();
|
||||
if (workedStatusIds.Count == 0) return 0;
|
||||
|
||||
var workedJobs = await _context.Set<Job>()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(j => j.CompanyId == company.Id && !j.IsDeleted
|
||||
&& workedStatusIds.Contains(j.JobStatusId))
|
||||
.ToListAsync();
|
||||
|
||||
if (workedJobs.Count == 0) return 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user