06/11 Update Inspection result will include GPS

This commit is contained in:
2026-06-11 13:57:41 -04:00
parent 99546f4ed7
commit 079e826a7a
6 changed files with 98 additions and 2 deletions
+4 -2
View File
@@ -63,8 +63,10 @@ class Inspection(db.Model):
status = db.Column(db.Enum('in_progress', 'completed', 'flagged'), default='in_progress')
notes = db.Column(db.Text) # inspector free-text notes
form_data = db.Column(db.JSON) # filled form field responses {field_id: value}
completed_at = db.Column(db.DateTime)
mobile_local_id = db.Column(db.String(64), nullable=True, index=True) # idempotency key for mobile submissions
completed_at = db.Column(db.DateTime)
mobile_local_id = db.Column(db.String(64), nullable=True, index=True)
submit_latitude = db.Column(db.Numeric(10, 7), nullable=True)
submit_longitude = db.Column(db.Numeric(10, 7), nullable=True)
# ── Re-inspection / follow-up workflow ────────────────────────────────
parent_inspection_id = db.Column(
+10
View File
@@ -533,6 +533,16 @@ def execute(inspection_id):
inspection.status = 'completed'
inspection.completed_at = now_eastern()
# Capture GPS coordinates submitted by the browser Geolocation API
try:
_lat = request.form.get('submit_lat', '').strip()
_lng = request.form.get('submit_lng', '').strip()
if _lat and _lng:
inspection.submit_latitude = float(_lat)
inspection.submit_longitude = float(_lng)
except (ValueError, TypeError):
pass
# Snapshot the current template schema so view() renders correctly
# even if the template is later edited or deleted.
_save_responses(inspection, responses, snapshot_schema=form_fields)
+19
View File
@@ -238,6 +238,8 @@
<div class="insp-wrap mt-3">
<form method="post" action="{{ url_for('inspections.execute', inspection_id=inspection.id) }}" enctype="multipart/form-data" id="inspectionForm" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="submit_lat" id="submitLat">
<input type="hidden" name="submit_lng" id="submitLng">
{# ── Header ── #}
<div class="insp-header">
@@ -889,6 +891,23 @@ document.getElementById('inspectionForm').addEventListener('submit', async funct
}
}
}
// Capture GPS on final submit (not draft) — fire-and-forget with short timeout
if (_submittingAction === 'submit' && navigator.geolocation) {
await new Promise(resolve => {
const timer = setTimeout(resolve, 4000); // give up after 4 s
navigator.geolocation.getCurrentPosition(
pos => {
document.getElementById('submitLat').value = pos.coords.latitude;
document.getElementById('submitLng').value = pos.coords.longitude;
clearTimeout(timer);
resolve();
},
() => { clearTimeout(timer); resolve(); }, // permission denied / unavailable — proceed without
{ timeout: 4000, maximumAge: 60000 }
);
});
}
// Native form submit — browser handles multipart encoding and follows the redirect
form.submit();
} catch (err) {
+26
View File
@@ -510,6 +510,32 @@
</div>
</div>
{# ── Submission GPS (admin / director only) ──────────────────────────── #}
{% if current_user.role in ['admin', 'director'] and inspection.submit_latitude and inspection.submit_longitude %}
{% set _lat = inspection.submit_latitude | float %}
{% set _lng = inspection.submit_longitude | float %}
{% set _maps_key = config.get('GOOGLE_MAPS_API_KEY', '') %}
<div class="mb-4">
<div class="lbl mb-1" style="font-size:.72rem;color:#94a3b8;font-weight:500;text-transform:uppercase;letter-spacing:.04em;">
<i class="bi bi-geo-alt-fill text-danger me-1"></i>Submission Location
</div>
{% if _maps_key %}
<div style="border-radius:10px;overflow:hidden;border:1px solid #e2e8f0;max-width:420px;">
<img src="https://maps.googleapis.com/maps/api/staticmap?center={{ _lat }},{{ _lng }}&zoom=15&size=420x220&maptype=roadmap&markers=color:red%7C{{ _lat }},{{ _lng }}&key={{ _maps_key }}"
alt="Submission location map"
style="width:100%;display:block;">
</div>
{% endif %}
<div class="mt-1" style="font-size:.8rem;color:#64748b;">
{{ '%.6f' | format(_lat) }}, {{ '%.6f' | format(_lng) }}
<a href="https://www.google.com/maps?q={{ _lat }},{{ _lng }}" target="_blank"
class="ms-2 text-decoration-none small">
<i class="bi bi-box-arrow-up-right"></i> Open in Maps
</a>
</div>
</div>
{% endif %}
{# ── Build the filtered field set ──────────────────────────────────────
Strategy:
1. Find all grid rows that contain at least one rated rating field.