diff --git a/src/PowderCoating.Web/Controllers/InvoicesController.cs b/src/PowderCoating.Web/Controllers/InvoicesController.cs index 9c9a7f6..6819a6f 100644 --- a/src/PowderCoating.Web/Controllers/InvoicesController.cs +++ b/src/PowderCoating.Web/Controllers/InvoicesController.cs @@ -464,16 +464,22 @@ public class InvoicesController : Controller // If the job came from a quote, carry over the quote-level costs and agreed terms. // The quote SubTotal = sum(items) + oven batch cost + shop supplies. // Job items only capture per-item prices, so oven & shop supplies need a separate line. - // Read directly from the quote snapshot — never try to reverse-engineer from job.FinalPrice - // because FinalPrice is recalculated on every item edit and can drift from the original quote. + // For fee components, prefer the job's own breakdown snapshot (updated every time the job + // is saved) over the source quote — the quote's FacilityOverheadCost was only added in + // migration AddQuotePricingSnapshotFields (May 2026); older quotes have 0 there even though + // overhead was included in the quote total. Tax and discount still come from the quote + // because those represent the customer-approved agreed terms. if (sourceQuote != null) { - // Bundle all quote-level charges so the invoice subtotal matches the quote total. - // FacilityOverheadCost is included — it is a real cost baked into the quoted price. - var processingFees = sourceQuote.OvenBatchCost - + sourceQuote.FacilityOverheadCost - + sourceQuote.ShopSuppliesAmount - + sourceQuote.RushFee; + // Prefer job breakdown values for dynamic fee components; fall back to quote for + // compatibility with jobs that were never re-saved after the May 2026 migration. + var ovenCost = jobBreakdown != null ? jobBreakdown.OvenBatchCost : sourceQuote.OvenBatchCost; + var overhead = jobBreakdown != null ? jobBreakdown.FacilityOverheadCost : sourceQuote.FacilityOverheadCost; + var shopSupplies = jobBreakdown != null ? jobBreakdown.ShopSuppliesAmount : sourceQuote.ShopSuppliesAmount; + var rushFee = jobBreakdown != null ? jobBreakdown.RushFee : sourceQuote.RushFee; + + // Bundle all quote-level charges so the invoice subtotal matches the job total. + var processingFees = ovenCost + overhead + shopSupplies + rushFee; if (processingFees > 0.01m) {