06/11 Update Inspection result will include GPS
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user