Fix sandblast-only toggle overflow and $0 AI quote pricing

Overflow: replaced Bootstrap form-check with an explicit flex row so the
two-line label (title + subtitle) never bleeds outside the card boundary.

$0 pricing: when sandblast-only was toggled on an AI item, manualUnitPrice
was cleared and isAiItem set to false. The pricing engine then returned $0
because no prep services with minutes were configured. Fix: preserve the AI
price when toggling sandblast-only, and keep isAiItem=true so the server
routes through the AI-price path (manualUnitPrice) rather than trying to
recalculate from prep labor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 14:29:03 -04:00
parent 0054b7d108
commit ecb285657a
+12 -11
View File
@@ -1461,10 +1461,12 @@ function aiShowError(message) {
function renderStep3Html() { function renderStep3Html() {
const isSandblastOnly = !!wz.data.sandblastOnly; const isSandblastOnly = !!wz.data.sandblastOnly;
return ` return `
<div class="form-check form-switch border rounded py-2 px-3 mb-3 bg-light"> <div class="d-flex align-items-center border rounded py-2 px-3 mb-3 bg-light gap-2">
<input class="form-check-input" type="checkbox" id="sandblastOnlyToggle" <div class="form-check form-switch mb-0 flex-shrink-0">
${isSandblastOnly ? 'checked' : ''} onchange="onSandblastOnlyToggle()"> <input class="form-check-input" type="checkbox" id="sandblastOnlyToggle"
<label class="form-check-label" for="sandblastOnlyToggle" style="line-height:1.3"> ${isSandblastOnly ? 'checked' : ''} onchange="onSandblastOnlyToggle()" style="cursor:pointer">
</div>
<label for="sandblastOnlyToggle" class="mb-0" style="line-height:1.3; cursor:pointer;">
<strong>Sandblast / Prep Only</strong> <strong>Sandblast / Prep Only</strong>
<span class="d-block text-muted fw-normal small">No powder coating — no oven or powder costs</span> <span class="d-block text-muted fw-normal small">No powder coating — no oven or powder costs</span>
</label> </label>
@@ -1491,10 +1493,8 @@ function onSandblastOnlyToggle() {
wz.data.sandblastOnly = checked; wz.data.sandblastOnly = checked;
if (checked) { if (checked) {
wz.data.coats = []; wz.data.coats = [];
// AI price was estimated with coating in mind — clear it so pricing recalculates from prep labor // Keep manualUnitPrice — if the AI returned a price, preserve it as the sandblast price.
if (wz.itemType === 'ai') { // The user can adjust it; clearing it causes a $0 quote when prep services aren't configured.
wz.data.manualUnitPrice = null;
}
} }
renderStep(3); renderStep(3);
} }
@@ -2309,9 +2309,10 @@ function preFillStep2() {
function buildItemFromWizard() { function buildItemFromWizard() {
const d = wz.data; const d = wz.data;
const isSandblastOnly = !!d.sandblastOnly; const isSandblastOnly = !!d.sandblastOnly;
// Sandblast-only AI items lose the AI pricing flag — the AI price included coating costs // AI flag is preserved even for sandblast-only so the server uses the AI price (manualUnitPrice).
// that no longer apply, so the server prices from prep labor instead. // Without this, sandblast-only AI items fall through to the pricing engine and return $0 when
const isAi = wz.itemType === 'ai' && !isSandblastOnly; // no prep services with minutes are configured.
const isAi = wz.itemType === 'ai';
return { return {
description: d.description || null, description: d.description || null,
quantity: d.quantity || 1, quantity: d.quantity || 1,