using Microsoft.EntityFrameworkCore;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;
namespace PowderCoating.Infrastructure.Services;
public partial class SeedDataService
{
///
/// Seeds 26 invoices spanning three months of billing history: six paid in month −3,
/// eight paid or partially paid in month −2, nine in month −1, and three recent plus
/// one overdue in the current month. Each invoice is linked to a seeded job and
/// has one or two line items plus an optional record.
///
///
///
/// Idempotency: returns 0 immediately if any non-deleted invoices already exist for this company.
///
///
/// The method resolves four accounting accounts by number/sub-type from the previously
/// seeded chart of accounts (4000 Powder Coating Revenue, 4100 Sandblasting Revenue,
/// 1000 Checking, 2200 Sales Tax Payable). If any account is not found its FK is simply
/// omitted (null) rather than aborting the seed run.
///
///
/// The private Inv() local async function handles building, saving, and optionally
/// recording a payment for a single invoice. It is a local function rather than a separate
/// method because it captures the outer variables jobs, seq, ji,
/// and the four account references by closure.
///
///
/// Payment dates follow the production convention: for fully-paid invoices the payment is
/// recorded five days before the due date; for partially-paid invoices the deposit is
/// recorded four days after the invoice date.
///
///
/// The "overdue" demo invoice is created 35 days ago with Net-14 terms, placing it 21 days
/// past due — enough to appear prominently in the AR Aging report.
///
///
/// The tenant company to seed invoices for.
/// Number of invoices inserted, or 0 if already seeded or no eligible jobs exist.
private async Task SeedInvoicesAsync(Company company)
{
var existingCount = await _context.Set()
.IgnoreQueryFilters()
.CountAsync(i => i.CompanyId == company.Id && !i.IsDeleted);
if (existingCount > 0)
return 0;
// ── Dependencies ──────────────────────────────────────────────────────
var jobs = await _context.Set()
.IgnoreQueryFilters()
.Include(j => j.JobItems)
.Where(j => j.CompanyId == company.Id && !j.IsDeleted
&& j.FinalPrice > 0 && j.CustomerId > 0)
.OrderBy(j => j.Id)
.ToListAsync();
if (jobs.Count == 0) return 0;
var revenueAcct = await _context.Set().IgnoreQueryFilters()
.FirstOrDefaultAsync(a => a.CompanyId == company.Id && a.AccountNumber == "4000" && !a.IsDeleted);
var sandblastAcct = await _context.Set().IgnoreQueryFilters()
.FirstOrDefaultAsync(a => a.CompanyId == company.Id && a.AccountNumber == "4100" && !a.IsDeleted);
var checkingAcct = await _context.Set().IgnoreQueryFilters()
.FirstOrDefaultAsync(a => a.CompanyId == company.Id
&& a.AccountSubType == AccountSubType.Checking && !a.IsDeleted);
var salesTaxAcct = await _context.Set().IgnoreQueryFilters()
.FirstOrDefaultAsync(a => a.CompanyId == company.Id && a.AccountNumber == "2200" && !a.IsDeleted);
var preparedBy = await _userManager.Users
.Where(u => u.CompanyId == company.Id).FirstOrDefaultAsync();
var now = DateTime.UtcNow;
var pfx = $"INV-{now:yy}{now.Month:D2}-";
var seq = 1;
var seeded = 0;
var ji = 0; // rotating job index
// Rotates through the available jobs in order. Using modulo wrapping means the
// seeder never throws even if there are fewer jobs than invoices to create.
Job NextJob() { var j = jobs[ji % jobs.Count]; ji++; return j; }
// ── Core builder ──────────────────────────────────────────────────────
//
// Builds and persists one Invoice + optional Payment in a single call.
// Each invoice is saved immediately (not batched) so EF generates an ID before
// the Payment FK is set. Tax is only added as a line item when taxPct > 0.
async Task Inv(
InvoiceStatus status,
int daysAgo, // invoice creation date
int dueDays, // net terms in days
decimal taxPct,
string terms,
string? notes,
// payment fields — ignored when status is Draft or Sent
PaymentMethod pMethod = PaymentMethod.BankTransferACH,
string? pRef = null)
{
var job = NextJob();
var date = now.AddDays(-daysAgo);
var sub = job.FinalPrice;
var taxAmt = Math.Round(sub * taxPct / 100m, 2);
var total = sub + taxAmt;
var isPaid = status == InvoiceStatus.Paid;
var isPart = status == InvoiceStatus.PartiallyPaid;
var amtPaid = isPaid ? total : isPart ? Math.Round(total * 0.5m, 2) : 0m;
var desc = job.JobItems?.FirstOrDefault()?.Description ?? "Powder Coating Services";
var inv = new Invoice
{
InvoiceNumber = $"{pfx}{seq++:D4}",
JobId = job.Id,
CustomerId = job.CustomerId,
PreparedById = preparedBy?.Id,
Status = status,
InvoiceDate = date,
DueDate = date.AddDays(dueDays),
SentDate = status != InvoiceStatus.Draft ? date : null,
PaidDate = isPaid ? date.AddDays(dueDays - 5) : null,
SubTotal = sub,
TaxPercent = taxPct,
TaxAmount = taxAmt,
Total = total,
AmountPaid = amtPaid,
Terms = terms,
Notes = notes,
CustomerPO = job.CustomerPO,
SalesTaxAccountId = taxAmt > 0 ? salesTaxAcct?.Id : null,
CompanyId = company.Id,
CreatedAt = date
};
inv.InvoiceItems.Add(new InvoiceItem
{
Description = desc,
Quantity = 1,
UnitPrice = sub,
TotalPrice = sub,
RevenueAccountId = revenueAcct?.Id,
DisplayOrder = 1,
CompanyId = company.Id,
CreatedAt = date
});
if (taxAmt > 0)
inv.InvoiceItems.Add(new InvoiceItem
{
Description = $"Sales Tax ({taxPct:0.##}%)",
Quantity = 1,
UnitPrice = taxAmt,
TotalPrice = taxAmt,
RevenueAccountId = salesTaxAcct?.Id,
DisplayOrder = 2,
CompanyId = company.Id,
CreatedAt = date
});
await _context.Set().AddAsync(inv);
await _context.SaveChangesAsync();
seeded++;
if (amtPaid > 0)
{
// Paid: payment date = dueDate - 5d. Partial: a few days after invoice
var pDate = isPaid ? date.AddDays(dueDays - 5) : date.AddDays(4);
await _context.Set().AddAsync(new Payment
{
InvoiceId = inv.Id,
Amount = amtPaid,
PaymentDate = pDate,
PaymentMethod = pMethod,
Reference = pRef,
Notes = isPaid ? "Paid in full" : "50% deposit",
RecordedById = preparedBy?.Id,
DepositAccountId = checkingAcct?.Id,
CompanyId = company.Id,
CreatedAt = pDate
});
await _context.SaveChangesAsync();
}
}
// ── Month −3 (6 paid) ─────────────────────────────────────────────────
await Inv(InvoiceStatus.Paid, 88, 30, 0m, "Net 30", "Thank you for your business!", PaymentMethod.BankTransferACH);
await Inv(InvoiceStatus.Paid, 84, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.Check, "CHK-1041");
await Inv(InvoiceStatus.Paid, 80, 14, 0m, "Net 14", "Thank you!", PaymentMethod.Cash);
await Inv(InvoiceStatus.Paid, 76, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.BankTransferACH);
await Inv(InvoiceStatus.Paid, 72, 30, 0m, "Net 30", "Thank you!", PaymentMethod.CreditDebitCard);
await Inv(InvoiceStatus.Paid, 68, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.Check, "CHK-1044");
// ── Month −2 (6 paid + 2 partial) ────────────────────────────────────
await Inv(InvoiceStatus.Paid, 62, 30, 0m, "Net 30", "Thank you!", PaymentMethod.BankTransferACH);
await Inv(InvoiceStatus.Paid, 58, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.Check, "CHK-1048");
await Inv(InvoiceStatus.Paid, 55, 14, 0m, "Due on receipt", "Paid on completion.", PaymentMethod.Cash);
await Inv(InvoiceStatus.Paid, 51, 30, 7.5m, "Net 30", "Thank you!", PaymentMethod.BankTransferACH);
await Inv(InvoiceStatus.Paid, 47, 30, 0m, "Net 30", "Thank you for your business!", PaymentMethod.CreditDebitCard);
await Inv(InvoiceStatus.Paid, 43, 30, 7.5m, "Net 30", "Thank you!", PaymentMethod.Check, "CHK-1052");
await Inv(InvoiceStatus.PartiallyPaid, 40, 30, 0m, "Net 30", "50% deposit received — balance due.", PaymentMethod.Check, "CHK-1053");
await Inv(InvoiceStatus.PartiallyPaid, 37, 30, 7.5m, "Net 30", "Deposit on file — balance due on pickup.", PaymentMethod.BankTransferACH);
// ── Month −1 (5 paid + 2 partial + 2 sent) ───────────────────────────
await Inv(InvoiceStatus.Paid, 32, 30, 0m, "Net 30", "Thank you!", PaymentMethod.BankTransferACH);
await Inv(InvoiceStatus.Paid, 28, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.Check, "CHK-1056");
await Inv(InvoiceStatus.Paid, 24, 14, 0m, "Net 14", "Thank you!", PaymentMethod.Cash);
await Inv(InvoiceStatus.Paid, 20, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.BankTransferACH);
await Inv(InvoiceStatus.Paid, 16, 30, 0m, "Net 30", "Thank you!", PaymentMethod.CreditDebitCard);
await Inv(InvoiceStatus.PartiallyPaid, 14, 30, 7.5m, "Net 30", "50% deposit received.", PaymentMethod.Check, "CHK-1060");
await Inv(InvoiceStatus.PartiallyPaid, 11, 30, 0m, "Net 30", "50% deposit — balance due on completion.", PaymentMethod.BankTransferACH);
await Inv(InvoiceStatus.Sent, 9, 30, 7.5m, "Net 30", "Payment due within 30 days.");
await Inv(InvoiceStatus.Sent, 6, 30, 0m, "Net 30", "Payment due within 30 days.");
// ── Current month (1 overdue + 2 sent + 1 draft) ─────────────────────
// Overdue: created 35 days ago on Net 14 terms → 21 days past due
await Inv(InvoiceStatus.Sent, 35, 14, 7.5m, "Net 14", "PAST DUE — please remit payment immediately.");
await Inv(InvoiceStatus.Sent, 4, 30, 0m, "Net 30", "Payment due within 30 days.");
await Inv(InvoiceStatus.Sent, 2, 30, 7.5m, "Net 30", "Payment due within 30 days.");
await Inv(InvoiceStatus.Draft, 1, 30, 0m, "Net 30", null);
return seeded;
}
}