Add oven batch cost to AI Quick Quote (1 batch, DefaultOvenCycleMinutes or 50 min)

Previously the quick quote omitted the oven charge entirely, so saved quotes
were under-priced relative to full quotes from the same items.

Pricing: CalculatePricing now calculates ovenBatchCost = (cycleMin/60) × OvenOperatingCostPerHour
using DefaultOvenCycleMinutes (fallback 50 min), then adds it to the total as a quote-level
charge matching how PricingCalculationService handles oven costs.

Save path: SaveQuickQuoteRequest gains OvenBatchCost + OvenCycleMinutes; the Quote record
now stores OvenBatchCost, OvenCycleMinutes, and Total = ItemsSubtotal + OvenBatchCost.

Display: results card shows a sub-line under the estimate price:
"incl. oven 1 batch 50 min: $12.00"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 15:20:10 -04:00
parent 9c4c20e8bd
commit 4d10175ce3
5 changed files with 34 additions and 9 deletions
@@ -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)
@@ -86,6 +86,7 @@
<div>
<div class="small text-muted">Estimate</div>
<div class="fs-5 fw-bold text-success" id="qq-res-price"></div>
<div id="qq-res-oven" class="text-muted" style="font-size:0.73rem;"></div>
</div>
<div class="text-end">
<div class="small text-muted">Confidence</div>
@@ -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);