Fix oven batch conversion, invoice quantity, AI photo pricing, and enforce pricing flag propagation
- Carry OvenBatches/OvenCycleMinutes from Quote → Job entity (was missing fields; all job pricing recalcs hardcoded 1/null) - Fix invoice creation from job always showing Quantity=1 (was using TotalPrice as UnitPrice with qty 1) - Add IsAiItem to JobItem + migration; map in all 3 JobItemAssemblyService.CreateJobItem overloads so AI photo jobs no longer double-price on first edit after quote→job conversion - Propagate IsAiItem through all existingItemsData JSON blocks in Jobs views (Edit, EditItems, Create) so the wizard preserves AI routing on re-edit - Add PricingRoutingFlags_ExistOnBothQuoteItemAndJobItem structural test + 3 behavioral IsAiItem tests to JobItemAssemblyServiceTests - Consolidate item wizard partials (_ItemWizardModal, _SqFtCalculatorModal) and item-wizard.css into shared locations - Document pricing flag propagation checklist in CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/* Item Wizard — shared styles used by all views that host the wizard modal */
|
||||
|
||||
/* Step indicator dots and connector lines */
|
||||
.wizard-step-dot {
|
||||
width: 22px; height: 22px; border-radius: 50%;
|
||||
background: #dee2e6; display: inline-block; cursor: default;
|
||||
border: 2px solid #dee2e6; transition: all .2s; flex-shrink: 0;
|
||||
}
|
||||
.wizard-step-dot.active { background: #0d6efd; border-color: #0d6efd; }
|
||||
.wizard-step-dot.done { background: #198754; border-color: #198754; }
|
||||
.wizard-step-dot.skip { background: #adb5bd; border-color: #adb5bd; }
|
||||
.wizard-step-line { flex: 1; height: 2px; background: #dee2e6; min-width: 30px; }
|
||||
|
||||
/* Item type picker cards (Step 1) */
|
||||
.item-type-card {
|
||||
border: 2px solid #dee2e6; border-radius: .75rem; padding: 1.25rem 1rem;
|
||||
cursor: pointer; transition: all .15s; text-align: center;
|
||||
background: #fff; user-select: none;
|
||||
}
|
||||
.item-type-card:hover { border-color: #86b7fe; background: #f0f6ff; }
|
||||
.item-type-card.selected { border-color: #0d6efd; background: #eef3ff; }
|
||||
.item-type-card .item-type-icon { font-size: 2rem; margin-bottom: .5rem; }
|
||||
[data-bs-theme="dark"] .item-type-card { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }
|
||||
[data-bs-theme="dark"] .item-type-card:hover { border-color: #86b7fe; background: var(--bs-secondary-bg); }
|
||||
[data-bs-theme="dark"] .item-type-card.selected { border-color: #0d6efd; background: #1a2a4a; }
|
||||
|
||||
/* Catalog listbox — custom scrollable list replacing a native <select> */
|
||||
.catalog-list-item { cursor: pointer; border-bottom: 1px solid var(--bs-border-color); font-size: .9rem; transition: background .1s; }
|
||||
.catalog-list-item:last-child { border-bottom: none; }
|
||||
.catalog-list-item:hover { background: var(--bs-tertiary-bg); }
|
||||
.catalog-list-item.selected { background: #eef3ff; color: #0d6efd; font-weight: 600; }
|
||||
[data-bs-theme="dark"] .catalog-list-item.selected { background: #1a2a4a; color: #86b7fe; }
|
||||
|
||||
/* Summary item cards (displayed below wizard after adding items) */
|
||||
.quote-item-card {
|
||||
border: 1px solid #dee2e6; border-radius: .5rem;
|
||||
padding: .75rem 1rem; margin-bottom: .5rem; background: #fafafa;
|
||||
}
|
||||
.quote-item-card .item-badge { font-size: .7rem; }
|
||||
[data-bs-theme="dark"] .quote-item-card { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }
|
||||
|
||||
/* Coat rows inside the wizard Step 3 */
|
||||
.coat-row { border: 1px solid #dee2e6; border-radius: .5rem; padding: .75rem; margin-bottom: .5rem; }
|
||||
[data-bs-theme="dark"] .coat-row { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); }
|
||||
@@ -3426,3 +3426,53 @@ function loadItemsFromTemplate(templateItems) {
|
||||
renderAllCards();
|
||||
scheduleAutoPricing();
|
||||
}
|
||||
|
||||
// ── Surface area calculator modal ─────────────────────────────────────────────
|
||||
let _sqFtTargetInput = null;
|
||||
|
||||
function openSqFtCalculator(inputId) {
|
||||
_sqFtTargetInput = inputId;
|
||||
document.getElementById('rectLength').value = 0;
|
||||
document.getElementById('rectWidth').value = 0;
|
||||
document.getElementById('calcResult').textContent = '0.00';
|
||||
new bootstrap.Modal(document.getElementById('sqFtCalculatorModal')).show();
|
||||
}
|
||||
|
||||
function toggleShapeInputs() {
|
||||
const shape = document.getElementById('calcShape').value;
|
||||
document.getElementById('rectangleInputs').style.display = shape === 'rectangle' ? 'block' : 'none';
|
||||
document.getElementById('cylinderInputs').style.display = shape === 'cylinder' ? 'block' : 'none';
|
||||
document.getElementById('circleInputs').style.display = shape === 'circle' ? 'block' : 'none';
|
||||
calculateSqFt();
|
||||
}
|
||||
|
||||
function calculateSqFt() {
|
||||
const useMetric = !!(pageMeta && pageMeta.useMetric);
|
||||
const divisor = useMetric ? 10000 : 144;
|
||||
const shape = document.getElementById('calcShape').value;
|
||||
let result = 0;
|
||||
if (shape === 'rectangle') {
|
||||
const l = parseFloat(document.getElementById('rectLength').value) || 0;
|
||||
const w = parseFloat(document.getElementById('rectWidth').value) || 0;
|
||||
result = (l * w) / divisor;
|
||||
} else if (shape === 'cylinder') {
|
||||
const d = parseFloat(document.getElementById('cylDiameter').value) || 0;
|
||||
const h = parseFloat(document.getElementById('cylHeight').value) || 0;
|
||||
const r = d / 2;
|
||||
result = (2 * Math.PI * r * r + 2 * Math.PI * r * h) / divisor;
|
||||
} else {
|
||||
const d = parseFloat(document.getElementById('circDiameter').value) || 0;
|
||||
const r = d / 2;
|
||||
result = (Math.PI * r * r) / divisor;
|
||||
}
|
||||
document.getElementById('calcResult').textContent = result.toFixed(4);
|
||||
}
|
||||
|
||||
function useSqFtResult() {
|
||||
const val = document.getElementById('calcResult').textContent;
|
||||
if (_sqFtTargetInput) {
|
||||
const el = document.getElementById(_sqFtTargetInput) || document.querySelector(`[name="${_sqFtTargetInput}"]`);
|
||||
if (el) { el.value = parseFloat(val).toFixed(2); el.dispatchEvent(new Event('change')); }
|
||||
}
|
||||
bootstrap.Modal.getInstance(document.getElementById('sqFtCalculatorModal'))?.hide();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user