diff --git a/src/PowderCoating.Application/DTOs/AI/AiQuickQuoteDtos.cs b/src/PowderCoating.Application/DTOs/AI/AiQuickQuoteDtos.cs index 2166558..85bc65c 100644 --- a/src/PowderCoating.Application/DTOs/AI/AiQuickQuoteDtos.cs +++ b/src/PowderCoating.Application/DTOs/AI/AiQuickQuoteDtos.cs @@ -65,6 +65,8 @@ public class SaveQuickQuoteRequest public decimal EstimatedUnitPrice { get; set; } public decimal MaterialCost { get; set; } public decimal LaborCost { get; set; } + public decimal OvenBatchCost { get; set; } + public int OvenCycleMinutes { get; set; } } /// Internal JSON schema returned by Claude for quick quote analysis. diff --git a/src/PowderCoating.Infrastructure/Services/AiQuickQuoteService.cs b/src/PowderCoating.Infrastructure/Services/AiQuickQuoteService.cs index 23bdf40..d86f90e 100644 --- a/src/PowderCoating.Infrastructure/Services/AiQuickQuoteService.cs +++ b/src/PowderCoating.Infrastructure/Services/AiQuickQuoteService.cs @@ -257,9 +257,14 @@ When estimating from a verbal description: subtotal = costs.ShopMinimumCharge; var unitPrice = Math.Max(0, Math.Round(subtotal, 2)); - var total = unitPrice * request.Quantity; var markupAmount = (materialCost + consumablesSurcharge) * (costs.GeneralMarkupPercentage / 100m); - var ovenCycleMinutes = costs.DefaultOvenCycleMinutes > 0 ? costs.DefaultOvenCycleMinutes : 45; + + // Oven batch charge: 1 batch, DefaultOvenCycleMinutes (fallback 50 min). + // Added at quote level (not baked into unitPrice) to match how the regular pricing engine works. + var ovenCycleMinutes = costs.DefaultOvenCycleMinutes > 0 ? costs.DefaultOvenCycleMinutes : 50; + var ovenBatchCost = Math.Round((ovenCycleMinutes / 60m) * costs.OvenOperatingCostPerHour, 2); + + var total = unitPrice * request.Quantity + ovenBatchCost; var breakdown = new AiPricingBreakdown { @@ -273,7 +278,7 @@ When estimating from a verbal description: MinFloorApplied = false, LaborCost = Math.Round(laborCost, 2), OvenCycleMinutes = ovenCycleMinutes, - OvenCost = 0m, + OvenCost = ovenBatchCost, RequiresPreheat = ai.RequiresPreheat, PreheatMinutes = preheatMinutes, PreheatCost = Math.Round(preheatCost, 2), diff --git a/src/PowderCoating.Web/Controllers/AiQuickQuoteController.cs b/src/PowderCoating.Web/Controllers/AiQuickQuoteController.cs index e111049..465ae4e 100644 --- a/src/PowderCoating.Web/Controllers/AiQuickQuoteController.cs +++ b/src/PowderCoating.Web/Controllers/AiQuickQuoteController.cs @@ -107,6 +107,9 @@ public class AiQuickQuoteController : Controller var quoteNumber = await GenerateQuoteNumberAsync(companyId); var now = DateTime.UtcNow; + var itemsSubtotal = request.EstimatedUnitPrice * request.Quantity; + var ovenCycleMinutes = request.OvenCycleMinutes > 0 ? request.OvenCycleMinutes : 50; + var quote = new Quote { CompanyId = companyId, @@ -121,12 +124,14 @@ public class AiQuickQuoteController : Controller CustomerPO = request.Reference, MaterialCosts = request.MaterialCost, LaborCosts = request.LaborCost, - ItemsSubtotal = request.EstimatedUnitPrice * request.Quantity, - SubTotal = request.EstimatedUnitPrice * request.Quantity, - Total = request.EstimatedUnitPrice * request.Quantity, + ItemsSubtotal = itemsSubtotal, + OvenBatches = 1, + OvenCycleMinutes = ovenCycleMinutes, + OvenBatchCost = request.OvenBatchCost, + SubTotal = itemsSubtotal, + Total = itemsSubtotal + request.OvenBatchCost, TaxPercent = 0, - TaxAmount = 0, - OvenBatches = 1 + TaxAmount = 0 }; if (draftStatus != null) diff --git a/src/PowderCoating.Web/Views/Shared/_AiQuickQuoteWidget.cshtml b/src/PowderCoating.Web/Views/Shared/_AiQuickQuoteWidget.cshtml index 44396f5..22efc29 100644 --- a/src/PowderCoating.Web/Views/Shared/_AiQuickQuoteWidget.cshtml +++ b/src/PowderCoating.Web/Views/Shared/_AiQuickQuoteWidget.cshtml @@ -86,6 +86,7 @@
Estimate
+
Confidence
diff --git a/src/PowderCoating.Web/wwwroot/js/ai-quick-quote.js b/src/PowderCoating.Web/wwwroot/js/ai-quick-quote.js index 344f28d..54301a8 100644 --- a/src/PowderCoating.Web/wwwroot/js/ai-quick-quote.js +++ b/src/PowderCoating.Web/wwwroot/js/ai-quick-quote.js @@ -29,6 +29,7 @@ resComplexity:document.getElementById('qq-res-complexity'), resMinutes: document.getElementById('qq-res-minutes'), resPrice: document.getElementById('qq-res-price'), + resOven: document.getElementById('qq-res-oven'), resConfidence:document.getElementById('qq-res-confidence'), resReasoning: document.getElementById('qq-res-reasoning'), powderSection:document.getElementById('qq-powder-section'), @@ -150,6 +151,15 @@ el.resComplexity.textContent = r.complexity || '—'; el.resMinutes.textContent = r.estimatedMinutes ? r.estimatedMinutes + ' min' : '—'; el.resPrice.textContent = formatCurrency(r.estimatedTotal || r.estimatedUnitPrice); + + const ovenCost = r.breakdown?.ovenCost; + const ovenMin = r.breakdown?.ovenCycleMinutes; + if (ovenCost && ovenCost > 0) { + el.resOven.textContent = `incl. oven 1 batch ${ovenMin ? ovenMin + ' min' : ''}: ${formatCurrency(ovenCost)}`; + } else { + el.resOven.textContent = ''; + } + el.resReasoning.textContent = r.reasoning || ''; // Confidence badge @@ -220,7 +230,9 @@ coatCount: parseInt(el.coats.value, 10) || 1, estimatedUnitPrice: lastResult.estimatedUnitPrice, materialCost: lastResult.breakdown?.materialCost ?? 0, - laborCost: lastResult.breakdown?.laborCost ?? 0 + laborCost: lastResult.breakdown?.laborCost ?? 0, + ovenBatchCost: lastResult.breakdown?.ovenCost ?? 0, + ovenCycleMinutes: lastResult.breakdown?.ovenCycleMinutes ?? 50 }; const response = await post('/AiQuickQuote/Save', body);