/** * Unified Lookup button for the Inventory Create/Edit forms. * * Flow: * 1. User fills in Manufacturer + Color Name (and/or Part Number) in the existing fields. * 2. Clicks "Lookup". * 3. This script searches the platform PowderCatalogItems table first (no API cost). * - 1 exact/best match → auto-fills fields immediately (same UX as AI Lookup). * - Multiple matches → Bootstrap modal lets user pick the right one. * - No match → falls through to window._runInventoryAiLookup() if AI is enabled. * 4. After a catalog hit, if AI is enabled, augments with cure data from the product URL. * * The AI-only button (#ai-lookup-btn) is still wired by _InventoryColorFamilyScripts.cshtml * and can be used to skip the catalog and go straight to AI. */ (function () { 'use strict'; const LOOKUP_URL = '/Inventory/CatalogLookup'; const AUGMENT_URL = '/Inventory/AiAugmentFromUrl'; const smartBtn = document.getElementById('smart-lookup-btn'); const statusEl = document.getElementById('ai-lookup-status'); // shared with AI lookup if (!smartBtn) return; // Snapshot of field values set by the catalog fill so we can clear them all // when the user starts typing a new color name. null when no catalog fill is active. let catalogSnapshot = null; // ── Button click ────────────────────────────────────────────────────────── smartBtn.addEventListener('click', async function () { const manufacturer = document.getElementById('field-manufacturer')?.value?.trim() || ''; const colorName = document.getElementById('field-colorname')?.value?.trim() || ''; const itemName = document.getElementById('field-name')?.value?.trim() || ''; // Don't use part number as the search term if the catalog previously filled it — // the snapshot tracks catalog-owned field values. const partNumberEl = document.getElementById('field-partnumber'); const partNumber = (catalogSnapshot?.['field-partnumber'] == null && partNumberEl?.value?.trim()) || ''; // Color name takes priority — it's what the user types when they want a specific powder. const searchTerm = colorName || itemName || partNumber; if (!searchTerm && !manufacturer) { showStatus('warning', 'Fill in at least a Color Name or Part Number, then click Lookup.'); return; } setLoading(true); showStatus('info', 'Searching catalog…'); try { const params = new URLSearchParams(); if (searchTerm) params.set('q', searchTerm); if (manufacturer) params.set('vendor', manufacturer); const currentId = smartBtn.dataset.currentId; if (currentId) params.set('currentId', currentId); const resp = await fetch(`${LOOKUP_URL}?${params}`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const items = await resp.json(); if (items.length === 0) { // No catalog match — fall back to AI if available hideStatus(); if (typeof window._runInventoryAiLookup === 'function') { showStatus('info', 'Not in catalog — searching with AI…'); await window._runInventoryAiLookup(); } else { showStatus('warning', 'No match found in the catalog. Enter details manually or enable AI Lookup.'); } return; } if (items.length === 1) { await fillFields(items[0]); return; } // Multiple matches — let the user pick via modal hideStatus(); showPickerModal(items); } catch (err) { showStatus('danger', 'Lookup failed: ' + err.message); } finally { setLoading(false); } }); // ── Fill fields from a catalog result ──────────────────────────────────── async function fillFields(item) { catalogSnapshot = {}; const filled = []; function setIf(id, value, label) { const el = document.getElementById(id); if (el && value != null && String(value).trim()) { el.value = String(value).trim(); catalogSnapshot[id] = String(value).trim(); filled.push(label); } } setIf('field-manufacturer', item.vendorName, 'Manufacturer'); setIf('field-partnumber', item.sku, 'Part Number'); setIf('field-colorname', item.colorName, 'Color Name'); // Name field (coating items use color name as name) const nameEl = document.getElementById('field-name'); if (nameEl && !nameEl.value.trim() && item.colorName) { nameEl.value = item.colorName; catalogSnapshot['field-name'] = item.colorName; filled.push('Name'); } // Description — only fill if currently empty const descEl = document.getElementById('field-description'); if (descEl && !descEl.value.trim() && item.description) { descEl.value = item.description; catalogSnapshot['field-description'] = item.description; filled.push('Description'); } // Unit cost — only fill if currently zero/empty const costEl = document.getElementById('field-unitcost'); if (costEl && item.unitPrice > 0 && (parseFloat(costEl.value) || 0) === 0) { costEl.value = item.unitPrice; catalogSnapshot['field-unitcost'] = String(item.unitPrice); filled.push('Unit Cost'); } // Coating specs — populated for scan-contributed entries; skip if already filled function setIfEmpty(id, value, label) { const el = document.getElementById(id); if (el && value != null && String(value).trim() && !el.value.trim()) { el.value = String(value).trim(); filled.push(label); } } setIfEmpty('field-finish', item.finish, 'Finish'); setIfEmpty('field-curetemp', item.cureTemperatureF, 'Cure Temp'); setIfEmpty('field-curetime', item.cureTimeMinutes, 'Cure Time'); setIfEmpty('field-coverage', item.coverageSqFtPerLb, 'Coverage'); setIfEmpty('field-specificgravity', item.specificGravity, 'Specific Gravity'); setIfEmpty('field-transfer', item.transferEfficiency,'Transfer Efficiency'); if (item.requiresClearCoat != null) { const cc = document.getElementById('field-clearcoat'); if (cc) { cc.checked = item.requiresClearCoat; filled.push('Clear Coat'); } } if (item.colorFamilies) { const hiddenInput = document.getElementById('field-colorfamilies'); if (hiddenInput && !hiddenInput.value.trim()) { const families = item.colorFamilies.split(',').map(s => s.trim()).filter(Boolean); hiddenInput.value = families.join(','); document.querySelectorAll('.color-family-chip').forEach(chip => { chip.classList.toggle('active', families.includes(chip.dataset.family)); }); filled.push('Color Families'); } } // Product URL + open-link button setIf('field-specpageurl', item.productUrl, 'Product URL'); syncLinkButton('field-specpageurl', 'field-specpageurl-link', item.productUrl); // SDS / TDS setIf('field-sdsurl', item.sdsUrl, 'SDS'); syncLinkButton('field-sdsurl', 'field-sdsurl-link', item.sdsUrl); setIf('field-tdsurl', item.tdsUrl, 'TDS'); syncLinkButton('field-tdsurl', 'field-tdsurl-link', item.tdsUrl); // Image if (item.imageUrl) { const imgInput = document.getElementById('field-imageurl'); const imgEl = document.getElementById('field-imagepreview-img'); const imgWrap = document.getElementById('wrap-imagepreview'); if (imgInput) { imgInput.value = item.imageUrl; catalogSnapshot['field-imageurl'] = item.imageUrl; } if (imgEl) imgEl.src = item.imageUrl; if (imgWrap) imgWrap.style.display = ''; filled.push('Image'); } // Vendor dropdown — match by name const vendorSel = document.getElementById('field-vendor'); if (vendorSel && !vendorSel.value && item.vendorName) { const needle = item.vendorName.toLowerCase(); const match = Array.from(vendorSel.options).find(o => o.text.toLowerCase().includes(needle) || needle.includes(o.text.toLowerCase().trim()) ); if (match) { vendorSel.value = match.value; filled.push('Vendor'); } } const discontinuedNote = item.isDiscontinued ? ' Discontinued' : ''; if (filled.length > 0) { showStatus('success', `Filled from catalog: ${filled.join(', ')}.${discontinuedNote}`); } else { showStatus('info', `Found in catalog but no empty fields to fill.${discontinuedNote}`); } // Augment with AI if enabled and we have a product URL with cure data to fetch if (item.productUrl && typeof window._runInventoryAiLookup === 'function') { await augmentFromUrl(item.productUrl, item.colorName, filled, discontinuedNote); } } // ── AI augmentation from product URL ──────────────────────────────────── async function augmentFromUrl(productUrl, colorName, alreadyFilled, discontinuedNote) { smartBtn.innerHTML = 'Augmenting with AI…'; showStatus('info', '' + 'Filled from catalog — fetching cure specs with AI…'); try { const fd = new FormData(); fd.append('productUrl', productUrl); if (colorName) fd.append('colorName', colorName); const resp = await fetch(AUGMENT_URL, { method: 'POST', body: fd }); if (!resp.ok) { // Restore the plain catalog success message and bail showStatus('success', `Filled from catalog: ${alreadyFilled.join(', ')}.${discontinuedNote}`); return; } const data = await resp.json(); if (!data.success) { showStatus('success', `Filled from catalog: ${alreadyFilled.join(', ')}.${discontinuedNote}`); return; } const augFilled = []; function setIfEmpty(id, value, label) { const el = document.getElementById(id); if (el && value != null && String(value).trim() && !el.value.trim()) { el.value = String(value).trim(); augFilled.push(label); } } setIfEmpty('field-finish', data.finish, 'Finish'); setIfEmpty('field-coverage', data.coverageSqFtPerLb, 'Coverage'); setIfEmpty('field-specificgravity', data.specificGravity, 'Specific Gravity'); setIfEmpty('field-transfer', data.transferEfficiency, 'Transfer Efficiency'); setIfEmpty('field-curetemp', data.cureTemperatureF, 'Cure Temp'); setIfEmpty('field-curetime', data.cureTimeMinutes, 'Cure Time'); if (data.requiresClearCoat !== null && data.requiresClearCoat !== undefined) { const cc = document.getElementById('field-clearcoat'); if (cc) { cc.checked = data.requiresClearCoat; augFilled.push('Clear Coat'); } } // Color families — only set if not already chosen if (data.colorFamilies) { const hiddenInput = document.getElementById('field-colorfamilies'); if (hiddenInput && !hiddenInput.value.trim()) { const families = data.colorFamilies.split(',').map(s => s.trim()).filter(Boolean); hiddenInput.value = families.join(','); document.querySelectorAll('.color-family-chip').forEach(chip => { chip.classList.toggle('active', families.includes(chip.dataset.family)); }); augFilled.push('Color Families'); } } // Image — only if catalog didn't provide one if (data.imageUrl && !document.getElementById('field-imageurl')?.value?.trim()) { const imgInput = document.getElementById('field-imageurl'); const imgEl = document.getElementById('field-imagepreview-img'); const imgWrap = document.getElementById('wrap-imagepreview'); if (imgInput) imgInput.value = data.imageUrl; if (imgEl) imgEl.src = data.imageUrl; if (imgWrap) imgWrap.style.display = ''; augFilled.push('Image'); } const allFilled = [...alreadyFilled, ...augFilled]; if (augFilled.length > 0) { showStatus('success', `Filled from catalog + AI: ${allFilled.join(', ')}.${discontinuedNote}`); } else { showStatus('success', `Filled from catalog: ${alreadyFilled.join(', ')}.${discontinuedNote}`); } } catch (err) { // AI augment is optional — restore the catalog success message showStatus('success', `Filled from catalog: ${alreadyFilled.join(', ')}.${discontinuedNote}`); } finally { // Always restore button label — the outer click handler manages disabled state // for single-match path, but the modal picker path needs this finally to reset it. smartBtn.innerHTML = 'Lookup'; } } // ── Clear all catalog-filled fields ───────────────────────────────────── function clearCatalogFill() { if (!catalogSnapshot) return; Object.keys(catalogSnapshot).forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; }); // Clear image preview if catalog filled the image if (catalogSnapshot['field-imageurl']) { const imgEl = document.getElementById('field-imagepreview-img'); const imgWrap= document.getElementById('wrap-imagepreview'); if (imgEl) imgEl.src = ''; if (imgWrap) imgWrap.style.display = 'none'; } // Clear color families if they were set by augment const hiddenInput = document.getElementById('field-colorfamilies'); if (hiddenInput) { hiddenInput.value = ''; document.querySelectorAll('.color-family-chip').forEach(c => c.classList.remove('active')); } catalogSnapshot = null; hideStatus(); } // When user starts typing a new color name, clear all catalog-filled fields so the // next search uses the fresh value rather than catalog-owned data. const colorNameEl = document.getElementById('field-colorname'); if (colorNameEl) { colorNameEl.addEventListener('input', function () { if (catalogSnapshot && colorNameEl.value !== (catalogSnapshot['field-colorname'] || '')) { clearCatalogFill(); } }); } // ── Modal picker for multiple results ──────────────────────────────────── function showPickerModal(items) { // Remove any stale instance document.getElementById('catalogPickerModal')?.remove(); const rows = items.map((item, i) => { const img = item.imageUrl ? `` : `
`; const disc = item.isDiscontinued ? `Discontinued` : ''; return ` `; }).join(''); const modal = document.createElement('div'); modal.innerHTML = ` `; document.body.appendChild(modal); const bsModal = new bootstrap.Modal(document.getElementById('catalogPickerModal')); document.querySelectorAll('.catalog-pick-row').forEach(function (btn) { btn.addEventListener('click', function () { const idx = parseInt(this.dataset.idx, 10); bsModal.hide(); fillFields(items[idx]); }); }); bsModal.show(); } // ── Helpers ─────────────────────────────────────────────────────────────── function syncLinkButton(inputId, linkId, url) { const link = document.getElementById(linkId); if (!link) return; if (url) { link.href = url; link.classList.remove('d-none'); } else { link.classList.add('d-none'); } } function setLoading(on) { smartBtn.disabled = on; smartBtn.innerHTML = on ? 'Looking up…' : 'Lookup'; } function showStatus(type, msg) { if (!statusEl) return; statusEl.className = `alert alert-${type} py-2 small mb-3 alert-permanent`; statusEl.innerHTML = msg; statusEl.classList.remove('d-none'); } function hideStatus() { if (statusEl) statusEl.classList.add('d-none'); } function esc(str) { if (!str) return ''; return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } })();