Fix mode structure: Scan (barcode+OCR+gallery) + Manual only
- Removed separate OCR and Connect mode toggles — merged into Scan - Renamed Barcode mode to Scan with OCR fallback + gallery upload - Added captureScanOcr() — tap to capture frame for OCR when barcode fails - handleBarcode() now compares barcode vs OCR result, picks valid ID - Gallery photo picker integrated into Scan mode (OCR + EXIF GPS) - Removed old photoPreviewCard and connect-mode HTML sections - Removed orphaned functions referencing removed elements
This commit is contained in:
+182
-214
@@ -1217,26 +1217,39 @@
|
||||
<input type="file" id="photoPicker" accept="image/*" capture="environment" style="display:none;">
|
||||
<!-- Mode Toggles -->
|
||||
<div class="mode-toggles">
|
||||
<button class="mode-toggle active" data-mode="barcode" onclick="setAddAssetMode('barcode')">📷 Barcode</button>
|
||||
<button class="mode-toggle" data-mode="ocr" onclick="setAddAssetMode('ocr')">🔍 OCR</button>
|
||||
<button class="mode-toggle active" data-mode="barcode" onclick="setAddAssetMode('barcode')">📷 Scan</button>
|
||||
<button class="mode-toggle" data-mode="manual" onclick="setAddAssetMode('manual')">✏️ Manual</button>
|
||||
<button class="mode-toggle" data-mode="connect" onclick="setAddAssetMode('connect')">🏷️ Connect</button>
|
||||
<button class="photo-picker-btn" onclick="document.getElementById('photoPicker').click()">📱 Pick from Gallery</button>
|
||||
</div>
|
||||
|
||||
<!-- ── BARCODE MODE ──────────────────────────────────────────────── -->
|
||||
<!-- ── SCAN MODE (barcode + OCR + gallery upload) ────────────────── -->
|
||||
<div id="addBarcodeMode" class="add-mode active">
|
||||
<div class="card">
|
||||
<div class="card-title">Barcode Scanner</div>
|
||||
<div class="card-title">Scan</div>
|
||||
<div id="cameraArea" class="camera-area">
|
||||
<div id="cameraPlaceholder" class="camera-placeholder">
|
||||
<span class="cam-icon">📷</span>
|
||||
<div class="cam-hint">Tap to start camera</div>
|
||||
</div>
|
||||
<video id="cameraVideo" autoplay playsinline muted></video>
|
||||
<div class="ocr-capture-overlay" id="scanCaptureOverlay" style="display:none;">
|
||||
<button class="ocr-capture-btn" onclick="captureScanOcr()" title="Capture for OCR">📸</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scanStatus" class="status-bar">Point camera at a barcode</div>
|
||||
<div id="scanStatus" class="status-bar">Point camera at a barcode or sticker</div>
|
||||
<div id="scanResult" class="scan-result" style="display:none;"></div>
|
||||
<div style="display:flex;gap:6px;margin-top:6px;">
|
||||
<button class="btn btn-outline btn-sm" onclick="document.getElementById('photoPicker').click()" style="flex:1;">📱 Pick from Gallery</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Photo preview (from gallery) -->
|
||||
<div id="scanPhotoPreview" class="card" style="display:none;">
|
||||
<div class="card-title">📷 Photo Preview</div>
|
||||
<img id="scanPhotoThumb" style="width:100%;border-radius:8px;margin-bottom:8px;">
|
||||
<input type="hidden" id="scanMachineId" value="">
|
||||
<div id="scanGpsResult" class="gps-result" style="display:none;"></div>
|
||||
<div id="scanOcrResult" style="font-size:13px;color:var(--text2);"></div>
|
||||
<button class="btn btn-primary" onclick="createFromScanPhoto()" style="margin-top:6px;">➕ Create Asset</button>
|
||||
</div>
|
||||
|
||||
<!-- New Asset form (shown when machine_id not found) -->
|
||||
@@ -1268,45 +1281,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── OCR MODE ──────────────────────────────────────────────────── -->
|
||||
<div id="addOcrMode" class="add-mode">
|
||||
<div class="card">
|
||||
<div class="card-title">OCR Scanner — Photograph Sticker</div>
|
||||
<div id="ocrCameraArea" class="camera-area">
|
||||
<div id="ocrPlaceholder" class="camera-placeholder" onclick="startOcrCamera()">
|
||||
<span class="cam-icon">📸</span>
|
||||
<div class="cam-hint" style="margin-top:4px;">Tap to start camera</div>
|
||||
</div>
|
||||
<video id="ocrVideo" autoplay playsinline muted style="display:none;"></video>
|
||||
<div class="ocr-capture-overlay" id="ocrCaptureOverlay" style="display:none;">
|
||||
<button class="ocr-capture-btn" onclick="captureOcr()" title="Capture">📸</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ocrStatus" class="status-bar">Point camera at the machine sticker and tap capture</div>
|
||||
<div id="ocrProgressWrap" class="ocr-progress-wrap">
|
||||
<div id="ocrProgressBar" class="ocr-progress-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- OCR result -->
|
||||
<div id="ocrResult" class="ocr-result" style="display:none;"></div>
|
||||
<!-- Quick create from OCR -->
|
||||
<div id="ocrCreateCard" class="card" style="display:none;">
|
||||
<div class="card-title">Create from OCR</div>
|
||||
<input id="ocrMachineId" type="text" class="input-field" placeholder="Machine ID" readonly>
|
||||
<input id="ocrName" type="text" class="input-field" placeholder="Asset name *" required>
|
||||
<select id="ocrCatSelect" class="input-field">
|
||||
<option value="">Category</option>
|
||||
</select>
|
||||
<select id="ocrStatus" class="input-field">
|
||||
<option value="active">Active</option>
|
||||
<option value="maintenance">Maintenance</option>
|
||||
<option value="retired">Retired</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="createOcrAsset()">Create Asset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── MANUAL MODE ───────────────────────────────────────────────── -->
|
||||
<!-- ── MANUAL MODE ──────────────────────────────────────────────── -->
|
||||
<div id="addManualMode" class="add-mode">
|
||||
<div class="card">
|
||||
<div class="card-title">Manual Entry</div>
|
||||
@@ -1414,75 +1389,8 @@
|
||||
<textarea id="manCheckinNotes" class="input-field" placeholder="Notes (optional)" rows="2"></textarea>
|
||||
<button class="btn btn-green" onclick="quickCheckinManual()" style="width:100%;">✓ Check In Now</button>
|
||||
</div>
|
||||
</div> </div>
|
||||
|
||||
<!-- ── CONNECT LABEL MODE (T4: unified photo + OCR + GPS) ──────────── -->
|
||||
<div id="addConnectMode" class="add-mode">
|
||||
<div class="card">
|
||||
<div class="card-title">🏷️ Scan Connect Label — Photo + OCR + GPS</div>
|
||||
<p style="font-size:12px;color:var(--text2);margin-bottom:10px;">
|
||||
Take or pick a photo of a machine sticker. OCR reads the ID, GPS is extracted automatically.
|
||||
</p>
|
||||
<div style="display:flex;gap:8px;margin-bottom:4px;">
|
||||
<button class="btn btn-primary" onclick="connectFromCamera()" style="flex:1;">
|
||||
📷 Camera
|
||||
</button>
|
||||
<button class="btn btn-outline" onclick="connectFromGallery()" style="flex:1;">
|
||||
🖼️ From Gallery
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden camera for connect mode -->
|
||||
<div id="connectCameraArea" class="camera-area" style="display:none;">
|
||||
<video id="connectVideo" autoplay playsinline muted style="display:none;width:100%;height:100%;object-fit:cover;position:absolute;inset:0;"></video>
|
||||
<div id="connectCameraPlaceholder" class="camera-placeholder" style="display:none;">
|
||||
<span class="cam-icon">📸</span>
|
||||
<div class="cam-hint">Tap to start camera</div>
|
||||
</div>
|
||||
<div class="ocr-capture-overlay" id="connectCaptureOverlay" style="display:none;">
|
||||
<button class="ocr-capture-btn" onclick="captureConnectPhoto()" title="Capture">📸</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Processing status -->
|
||||
<div id="connectStatus" class="status-bar" style="display:none;"></div>
|
||||
|
||||
<!-- Results card -->
|
||||
<div id="connectResults" class="card" style="display:none;">
|
||||
<div class="card-title">📋 Scan Results</div>
|
||||
<img id="connectThumb" src="" alt="Scanned photo" style="width:100%;max-height:200px;object-fit:cover;border-radius:var(--radius-sm);margin-bottom:10px;">
|
||||
<div id="connectOcrResult" style="margin-bottom:8px;"></div>
|
||||
<div id="connectGpsResult" style="margin-bottom:8px;"></div>
|
||||
<div id="connectMiniMap" style="display:none;width:100%;height:160px;border-radius:var(--radius-sm);overflow:hidden;margin-bottom:8px;"></div>
|
||||
|
||||
<!-- Editable fields -->
|
||||
<input id="connectMachineId" type="text" class="input-field" placeholder="Machine ID *">
|
||||
<input id="connectName" type="text" class="input-field" placeholder="Asset Name *">
|
||||
<div class="form-row" style="margin-bottom:8px;">
|
||||
<input id="connectLat" type="number" step="any" class="input-field" placeholder="Latitude">
|
||||
<input id="connectLng" type="number" step="any" class="input-field" placeholder="Longitude">
|
||||
</div>
|
||||
<select id="connectCatSelect" class="input-field"></select>
|
||||
|
||||
<div style="display:flex;gap:8px;">
|
||||
<button class="btn btn-primary" onclick="createConnectAsset()" style="flex:2;">➕ Create Asset</button>
|
||||
<button class="btn btn-outline" onclick="resetConnectMode()" style="flex:1;">🔄 Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Photo Preview Card (shown when a photo is picked from gallery) ── -->
|
||||
<div id="photoPreviewCard" class="photo-preview-card" style="display:none;">
|
||||
<img id="pickedPhotoPreview" src="" alt="Picked photo">
|
||||
<div class="photo-preview-toolbar">
|
||||
<button onclick="ocrPickedPhoto()">🔍 OCR this photo</button>
|
||||
<button onclick="extractGpsFromPicked()">📍 Extract GPS</button>
|
||||
<button onclick="createAssetFromPicked()">➕ Create Asset</button>
|
||||
</div>
|
||||
<div class="exif-data" id="pickedExifData" style="display:none;"></div>
|
||||
<button class="btn btn-outline btn-sm" style="margin-top:8px;width:100%;" onclick="clearPickedPhoto()">✕ Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════════════
|
||||
TAB: ASSETS LIST
|
||||
@@ -2667,9 +2575,7 @@
|
||||
|
||||
// Stop any running scanners
|
||||
stopScanning();
|
||||
stopOcrCamera();
|
||||
stopManualPhoto();
|
||||
stopConnectCamera();
|
||||
|
||||
if (mode === 'barcode') {
|
||||
startScanning();
|
||||
@@ -2695,63 +2601,88 @@
|
||||
function handlePickedPhoto(file) {
|
||||
pickedPhotoFile = file;
|
||||
pickedPhotoGps = null;
|
||||
|
||||
// Switch to Scan mode
|
||||
setAddAssetMode('barcode');
|
||||
|
||||
// Show thumbnail in scan photo preview
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
document.getElementById('pickedPhotoPreview').src = e.target.result;
|
||||
document.getElementById('photoPreviewCard').style.display = 'block';
|
||||
reader.onload = async function(e) {
|
||||
const thumb = document.getElementById('scanPhotoThumb');
|
||||
if (thumb) thumb.src = e.target.result;
|
||||
|
||||
// Extract EXIF / file metadata
|
||||
extractExifData(file);
|
||||
const preview = document.getElementById('scanPhotoPreview');
|
||||
if (preview) preview.style.display = 'block';
|
||||
|
||||
// Try EXIF GPS extraction
|
||||
tryExtractGpsFromPicked();
|
||||
// Run OCR on the photo
|
||||
const ocrEl = document.getElementById('scanOcrResult');
|
||||
if (ocrEl) ocrEl.textContent = '⏳ Running OCR...';
|
||||
|
||||
// Scroll to preview
|
||||
document.getElementById('photoPreviewCard').scrollIntoView({ behavior: 'smooth' });
|
||||
try {
|
||||
const ocrData = await ocrImageClient(file);
|
||||
if (ocrData && ocrData.machine_id) {
|
||||
if (ocrEl) ocrEl.innerHTML = `✅ Machine ID: <strong>${esc(ocrData.machine_id)}</strong>` +
|
||||
(ocrData.confidence === 'low' ? ' ⚠️ Low confidence' : '');
|
||||
} else {
|
||||
if (ocrEl) ocrEl.innerHTML = '❌ No machine ID detected. <button class="btn btn-outline btn-sm" onclick="document.getElementById(\'scanMachineId\').value=prompt(\'Enter machine ID:\')">Enter manually</button>';
|
||||
}
|
||||
} catch (ocrErr) {
|
||||
if (ocrEl) ocrEl.textContent = '❌ OCR failed: ' + ocrErr.message;
|
||||
}
|
||||
|
||||
// Extract GPS from EXIF
|
||||
const gpsEl = document.getElementById('scanGpsResult');
|
||||
try {
|
||||
const gps = await extractGpsFromPhoto(file);
|
||||
if (gps && gps.lat != null && gps.lng != null) {
|
||||
pickedPhotoGps = gps;
|
||||
if (gpsEl) {
|
||||
gpsEl.style.display = 'block';
|
||||
gpsEl.innerHTML = `📍 GPS: ${gps.lat.toFixed(6)}, ${gps.lng.toFixed(6)}`;
|
||||
}
|
||||
} else {
|
||||
if (gpsEl) {
|
||||
gpsEl.style.display = 'block';
|
||||
gpsEl.innerHTML = '📍 No GPS data in photo';
|
||||
}
|
||||
}
|
||||
} catch (gpsErr) {
|
||||
if (gpsEl) {
|
||||
gpsEl.style.display = 'block';
|
||||
gpsEl.innerHTML = '📍 GPS error: ' + gpsErr.message;
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
function extractExifData(file) {
|
||||
const exifDiv = document.getElementById('pickedExifData');
|
||||
const rows = [];
|
||||
rows.push({ label: 'File name', value: file.name });
|
||||
rows.push({ label: 'File size', value: formatFileSize(file.size) });
|
||||
rows.push({ label: 'Type', value: file.type || 'unknown' });
|
||||
rows.push({ label: 'Last modified', value: formatDate(new Date(file.lastModified).toISOString()) });
|
||||
function createFromScanPhoto() {
|
||||
// Gather OCR + GPS results from scan photo and create asset
|
||||
const midEl = document.getElementById('scanMachineId');
|
||||
const mid = midEl ? midEl.value : '';
|
||||
const name = prompt('Asset name:', '');
|
||||
if (!mid || !name) return showToast('Machine ID and name required', true);
|
||||
|
||||
exifDiv.innerHTML = rows.map(r =>
|
||||
`<div class="exif-row"><span class="exif-label">${esc(r.label)}</span><span class="exif-value">${esc(r.value)}</span></div>`
|
||||
).join('');
|
||||
exifDiv.style.display = 'block';
|
||||
const payload = { machine_id: mid, name };
|
||||
if (pickedPhotoGps) {
|
||||
payload.latitude = pickedPhotoGps.lat;
|
||||
payload.longitude = pickedPhotoGps.lng;
|
||||
}
|
||||
|
||||
api('/api/connect-label', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
}).then(() => {
|
||||
showToast('✅ Asset created');
|
||||
document.getElementById('scanPhotoPreview').style.display = 'none';
|
||||
pickedPhotoFile = null;
|
||||
pickedPhotoGps = null;
|
||||
}).catch(e => showToast('Error: ' + e.message, true));
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / 1048576).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
function clearPickedPhoto() {
|
||||
pickedPhotoFile = null;
|
||||
pickedPhotoGps = null;
|
||||
document.getElementById('photoPreviewCard').style.display = 'none';
|
||||
document.getElementById('pickedPhotoPreview').src = '';
|
||||
document.getElementById('pickedExifData').style.display = 'none';
|
||||
document.getElementById('pickedExifData').innerHTML = '';
|
||||
// Reset the file input so the same file can be picked again
|
||||
const pp = document.getElementById('photoPicker');
|
||||
if (pp) pp.value = '';
|
||||
}
|
||||
|
||||
function ocrPickedPhoto() {
|
||||
if (!pickedPhotoFile) return;
|
||||
// Switch to OCR mode with the picked photo available
|
||||
setAddAssetMode('ocr');
|
||||
showToast('Photo loaded in preview — OCR processing ready');
|
||||
}
|
||||
|
||||
// ▶ Core EXIF GPS extraction using exifr.js (loaded in <head>)
|
||||
// ▶ Core EXIF GPS extraction using exifr.js
|
||||
async function extractGpsFromPhoto(file) {
|
||||
try {
|
||||
if (typeof exifr === 'undefined') {
|
||||
@@ -2772,61 +2703,9 @@
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ▶ Try to extract GPS from the currently picked photo and render results
|
||||
async function tryExtractGpsFromPicked() {
|
||||
if (!pickedPhotoFile) return;
|
||||
const exifEl = document.getElementById('pickedExifData');
|
||||
|
||||
// Append GPS status to existing EXIF display
|
||||
let gpsSection = document.getElementById('pickedGpsSection');
|
||||
if (!gpsSection) {
|
||||
gpsSection = document.createElement('div');
|
||||
gpsSection.id = 'pickedGpsSection';
|
||||
gpsSection.style.marginTop = '8px';
|
||||
gpsSection.style.paddingTop = '8px';
|
||||
gpsSection.style.borderTop = '1px solid var(--border)';
|
||||
exifEl.appendChild(gpsSection);
|
||||
}
|
||||
gpsSection.innerHTML = '<span style="color:var(--amber);">⏳ Reading GPS from photo...</span>';
|
||||
|
||||
const gps = await extractGpsFromPhoto(pickedPhotoFile);
|
||||
pickedPhotoGps = gps;
|
||||
|
||||
if (gps) {
|
||||
gpsSection.innerHTML = `
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px;">
|
||||
<span class="gps-badge ok">📍 GPS from photo</span>
|
||||
</div>
|
||||
<div style="font-size:13px;color:var(--text2);margin-bottom:4px;">
|
||||
${gps.lat.toFixed(6)}, ${gps.lng.toFixed(6)}
|
||||
${gps.accuracy ? ` (±${gps.accuracy.toFixed(1)}m)` : ''}
|
||||
</div>
|
||||
<div id="gpsMiniMap"></div>
|
||||
<div style="display:flex;gap:6px;margin-top:8px;">
|
||||
<button class="btn btn-sm btn-outline" onclick="fillManualFromGps()" style="flex:1;font-size:12px;">
|
||||
📝 Fill Form
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline" onclick="openInMaps()" style="flex:1;font-size:12px;">
|
||||
🗺️ Maps
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
// Render mini-map
|
||||
const miniMapEl = document.getElementById('gpsMiniMap');
|
||||
if (miniMapEl) {
|
||||
setTimeout(() => renderGpsPreview(gps.lat, gps.lng, miniMapEl), 100);
|
||||
}
|
||||
} else {
|
||||
gpsSection.innerHTML = `
|
||||
<span class="gps-badge err">📍 No GPS in photo</span>
|
||||
<div style="font-size:11px;color:var(--text3);margin-top:4px;">
|
||||
Phones often strip EXIF GPS when sharing. You can enter coordinates manually.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ▶ Render a mini-map preview using Leaflet
|
||||
function renderGpsPreview(lat, lng, containerEl) {
|
||||
if (!containerEl) return;
|
||||
@@ -3164,6 +3043,55 @@
|
||||
if (el) { el.textContent = msg; el.className = 'status-bar ' + cls; }
|
||||
}
|
||||
|
||||
async function captureScanOcr() {
|
||||
const video = document.getElementById('cameraVideo');
|
||||
if (!video || video.readyState < 2) {
|
||||
setScanStatus('Camera not ready — tap to start it first', 'error');
|
||||
return;
|
||||
}
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.getContext('2d').drawImage(video, 0, 0);
|
||||
const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', 0.85));
|
||||
setScanStatus('Processing OCR...', 'working');
|
||||
|
||||
try {
|
||||
// Try server first with short timeout
|
||||
const controller = new AbortController();
|
||||
const t = setTimeout(() => controller.abort(), 6000);
|
||||
const fd = new FormData();
|
||||
fd.append('file', blob, 'sticker.jpg');
|
||||
const res = await fetch('/api/ocr', {
|
||||
method: 'POST', body: fd, signal: controller.signal,
|
||||
headers: AppState.authToken
|
||||
? { 'Authorization': 'Bearer ' + AppState.authToken } : {},
|
||||
});
|
||||
clearTimeout(t);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data && data.machine_id) {
|
||||
handleBarcode(data.machine_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error('Server OCR failed');
|
||||
} catch (e) {
|
||||
// Fall back to client-side Tesseract.js
|
||||
try {
|
||||
const data = await ocrImageClient(blob);
|
||||
if (data && data.machine_id) {
|
||||
handleBarcode(data.machine_id);
|
||||
return;
|
||||
}
|
||||
} catch (clientErr) {
|
||||
setScanStatus('OCR failed: ' + clientErr.message, 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
setScanStatus('No machine ID detected in frame', 'error');
|
||||
}
|
||||
|
||||
async function handleBarcode(machineId) {
|
||||
if (barcodeDebounce) return;
|
||||
barcodeDebounce = machineId;
|
||||
@@ -3174,6 +3102,46 @@
|
||||
document.getElementById('newAssetCard').style.display = 'none';
|
||||
document.getElementById('checkinCard').style.display = 'none';
|
||||
|
||||
// Also OCR the current frame to compare with barcode result
|
||||
try {
|
||||
const video = document.getElementById('cameraVideo');
|
||||
if (video && video.readyState >= 2) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.getContext('2d').drawImage(video, 0, 0);
|
||||
const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', 0.8));
|
||||
if (blob && blob.size > 0) {
|
||||
const ocrData = await ocrImageClient(blob);
|
||||
if (ocrData && ocrData.machine_id) {
|
||||
console.log(`Barcode: ${machineId}, OCR: ${ocrData.machine_id}`);
|
||||
// Prefer the one that looks more like a valid machine ID
|
||||
// Machine ID pattern: 5 digits (last 5 of XXXXX-XXXXXX)
|
||||
const barcodeDigits = machineId.replace(/\D/g, '');
|
||||
const ocrDigits = ocrData.machine_id.replace(/\D/g, '');
|
||||
const barcodeValid = barcodeDigits.length >= 5;
|
||||
const ocrValid = ocrDigits.length >= 5 && ocrData.confidence !== 'low';
|
||||
if (ocrValid && !barcodeValid) {
|
||||
console.log('Using OCR result over barcode');
|
||||
machineId = ocrData.machine_id;
|
||||
currentScannedMachineId = machineId;
|
||||
} else if (ocrValid && barcodeValid) {
|
||||
// Both valid — check which matches the XXXXX-XXXXXX pattern better
|
||||
const barcodeIsId = machineId.includes('-') || /^\d{4,6}$/.test(machineId.trim());
|
||||
const ocrIsId = ocrData.machine_id.includes('-') || /^\d{4,6}$/.test(ocrData.machine_id);
|
||||
if (ocrIsId && !barcodeIsId) {
|
||||
console.log('OCR result matches ID format better');
|
||||
machineId = ocrData.machine_id;
|
||||
currentScannedMachineId = machineId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ocrErr) {
|
||||
console.warn('OCR comparison failed:', ocrErr.message);
|
||||
}
|
||||
|
||||
try {
|
||||
const asset = await api(`/api/assets/search?machine_id=${encodeURIComponent(machineId)}`);
|
||||
autoCheckin(asset.id);
|
||||
|
||||
Reference in New Issue
Block a user