namespace PowderCoating.Web.ViewModels.Reports;
public class JobProfitabilityViewModel : ReportViewModelBase
{
public bool TimeTrackedOnly { get; set; }
// ── Summary KPIs ──────────────────────────────────────────────────────
public int TotalJobs { get; set; }
public int JobsWithTimeEntries { get; set; }
public decimal TotalRevenue { get; set; }
public decimal TotalCollected { get; set; }
public decimal TotalEstimatedCost { get; set; }
public decimal TotalActualHours { get; set; }
/// Average margin % across jobs that have at least some cost data.
public decimal AvgMarginPercent { get; set; }
// ── Detail rows ───────────────────────────────────────────────────────
public List Items { get; set; } = new();
}
public class JobProfitabilityItem
{
public int JobId { get; set; }
public string JobNumber { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
public string StatusName { get; set; } = string.Empty;
public string StatusColorClass { get; set; } = "bg-secondary";
public DateTime JobDate { get; set; }
/// The invoiced / final price of the job.
public decimal FinalPrice { get; set; }
/// How much has actually been collected on the linked invoice.
public decimal AmountCollected { get; set; }
/// Total hours logged via time entries.
public decimal ActualHours { get; set; }
/// ActualHours × StandardLaborRate.
public decimal ActualLaborCost { get; set; }
///
/// Sum of (ActualPowderUsedLbs ?? PowderToOrder) × PowderCostPerLb across all coats.
/// Zero when no coat has pricing data.
///
public decimal ActualPowderCost { get; set; }
public decimal EstimatedTotalCost => ActualLaborCost + ActualPowderCost;
public decimal GrossMargin => FinalPrice - EstimatedTotalCost;
public decimal MarginPercent => FinalPrice > 0
? Math.Round(GrossMargin / FinalPrice * 100, 1)
: 0;
/// True when at least one JobTimeEntry exists for this job.
public bool HasTimeEntries { get; set; }
}