From eaab0af51f107a89c1b29cdd93b558bd5b80282b Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Wed, 20 May 2026 22:18:52 -0400 Subject: [PATCH] Fix facility overhead missing from invoices on quote-based jobs For quote-based jobs, invoice creation now reads fee components (oven, facility overhead, shop supplies, rush fee) from the job's PricingBreakdownJson snapshot rather than the source quote. The FacilityOverheadCost column was added to Quotes in May 2026; older quotes have 0 there even though overhead was included in their total, causing invoices to silently drop the overhead charge. The job snapshot is updated on every save so it always reflects the current pricing. Tax rate and discount still come from the source quote as agreed terms. Co-Authored-By: Claude Sonnet 4.6 --- .../Controllers/InvoicesController.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) 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) {