feat: OCR auto-saves GPS to asset + MSFS enrichment in detail view
OCR endpoint (/api/ocr) now auto-saves EXIF GPS from the photo
to the asset's latitude/longitude fields when they are blank,
so the first photo scan at a machine permanently records its location.
GET /api/assets/{id} now includes an 'msfs' block enriched from
the merged Dynamics 365 Field Service data (merged-assets.json)
loaded at server start (1,834 matched assets). Fields shown:
equipment_id, connect_id, compass_asset_number, manufacturer,
model_text, dex_model, install_date, software_version, etc.
Frontend updates:
- New 'Field Service Data' card in asset detail view when MSFS
data is available (manufacturer, Connect ID, install date, etc.)
- '📍 GPS saved to asset' confirmation shown after OCR auto-save
- AppState.ocrGpsSaved flag to track the auto-save event
This commit is contained in:
@@ -1309,6 +1309,12 @@
|
||||
<div id="detailSEFields"></div>
|
||||
</div>
|
||||
|
||||
<!-- MSFS Data (Dynamics 365 Field Service enrichment) -->
|
||||
<div class="card" id="detailMsfsCard" style="display:none;">
|
||||
<div class="card-title">📊 Field Service Data</div>
|
||||
<div id="detailMsfsFields"></div>
|
||||
</div>
|
||||
|
||||
<!-- Check-in History -->
|
||||
<div class="card">
|
||||
<div class="card-title">Check-in History <span id="checkinCount" style="color:var(--accent2);"></span></div>
|
||||
@@ -1688,6 +1694,7 @@
|
||||
gpsAcc: null,
|
||||
currentTab: 'tabAddAsset',
|
||||
currentAssetId: null,
|
||||
ocrGpsSaved: false, // true when OCR photo GPS was auto-saved to asset
|
||||
};
|
||||
|
||||
function isLoggedIn() { return !!AppState.authToken; }
|
||||
@@ -2773,6 +2780,9 @@
|
||||
}
|
||||
showToast('📍 GPS extracted from photo: ' + data.exif_gps.lat.toFixed(6) + ', ' + data.exif_gps.lng.toFixed(6));
|
||||
}
|
||||
if (data.gps_saved) {
|
||||
AppState.ocrGpsSaved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ── OCR: Auto-search asset by machine_id ─────────────────────────────
|
||||
@@ -2793,6 +2803,10 @@
|
||||
doAutoCheckin(asset.id);
|
||||
el.innerHTML += `<div style="margin-top:8px;font-size:12px;color:var(--green);">✓ Auto check-in recorded</div>`;
|
||||
}
|
||||
if (AppState.ocrGpsSaved) {
|
||||
el.innerHTML += `<div style="margin-top:8px;font-size:12px;color:var(--green);">📍 GPS saved to asset</div>`;
|
||||
AppState.ocrGpsSaved = false;
|
||||
}
|
||||
el.innerHTML += `<div style="margin-top:8px;">
|
||||
<button class="btn btn-outline btn-sm" onclick="switchTab('tabAssets');viewAsset(${asset.id});" style="width:100%;">View Details</button>
|
||||
</div>`;
|
||||
@@ -3791,6 +3805,35 @@
|
||||
// Service Entrances
|
||||
await loadServiceEntrances(a);
|
||||
|
||||
// MSFS Data (Dynamics 365 enrichment)
|
||||
const msfsCard = document.getElementById('detailMsfsCard');
|
||||
const msfsFields = document.getElementById('detailMsfsFields');
|
||||
if (a.msfs) {
|
||||
const m = a.msfs;
|
||||
const msfsRows = [
|
||||
df('Equipment ID', m.equipment_id, true),
|
||||
df('Connect ID', m.connect_id, true),
|
||||
df('Compass #', m.compass_asset_number, true),
|
||||
df('Manufacturer', m.manufacturer),
|
||||
df('Model', m.model_text),
|
||||
df('DEX Model', m.dex_model),
|
||||
df('Canteen Connect GUID', m.canteen_connect_guid, true),
|
||||
df('Account Name', m.account_name),
|
||||
df('Install Date', m.install_date),
|
||||
df('Manufacture Date', m.manufacture_date),
|
||||
df('Software Version', m.software_version, true),
|
||||
df('Work Orders', m.work_order_count ? `${m.work_order_count} work orders` : null),
|
||||
].filter(Boolean).join('');
|
||||
if (msfsRows) {
|
||||
msfsFields.innerHTML = msfsRows;
|
||||
msfsCard.style.display = 'block';
|
||||
} else {
|
||||
msfsCard.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
msfsCard.style.display = 'none';
|
||||
}
|
||||
|
||||
await loadCheckinHistory(id);
|
||||
} catch (e) {
|
||||
showToast(e.message, true);
|
||||
|
||||
Reference in New Issue
Block a user