Enforce browser location for geo fenced attendance
This commit is contained in:
@@ -952,6 +952,27 @@ def _ip_matches_allowed(ip_value: str | None, allowed_csv: str | None) -> tuple[
|
|||||||
return False, "invalid_ip_rule" if invalid_rule_found else "outside_allowed_ip"
|
return False, "invalid_ip_rule" if invalid_rule_found else "outside_allowed_ip"
|
||||||
|
|
||||||
|
|
||||||
|
def _attendance_geo_is_enforced(settings: BranchSettings | None) -> bool:
|
||||||
|
"""Return True when branch GPS geofence is configured and must be captured by browser."""
|
||||||
|
if not settings or not bool(getattr(settings, "attendance_geo_enabled", False)):
|
||||||
|
return False
|
||||||
|
return _to_float(getattr(settings, "latitude", None)) is not None and _to_float(getattr(settings, "longitude", None)) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _raise_if_required_geo_missing(settings: BranchSettings | None, evaluation: dict[str, Any]) -> None:
|
||||||
|
"""Block self-attendance when geofencing is enabled but browser location is missing/invalid."""
|
||||||
|
if not _attendance_geo_is_enforced(settings):
|
||||||
|
return
|
||||||
|
if evaluation.get("geo_status") in {"location_missing", "invalid_location"}:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=(
|
||||||
|
"Location is required for attendance because branch geo-fencing is enabled. "
|
||||||
|
"Please allow browser location/GPS and try again."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _evaluate_attendance_controls(
|
def _evaluate_attendance_controls(
|
||||||
db: Session,
|
db: Session,
|
||||||
*,
|
*,
|
||||||
@@ -1071,6 +1092,7 @@ def punch_in_attendance(
|
|||||||
longitude=longitude,
|
longitude=longitude,
|
||||||
client_ip=client_ip,
|
client_ip=client_ip,
|
||||||
)
|
)
|
||||||
|
_raise_if_required_geo_missing(branch_settings, evaluation)
|
||||||
timing = _evaluate_attendance_timing(branch, branch_settings, local_dt)
|
timing = _evaluate_attendance_timing(branch, branch_settings, local_dt)
|
||||||
final_status = evaluation["status"] if evaluation["approval_status"] == "pending" else timing["status"]
|
final_status = evaluation["status"] if evaluation["approval_status"] == "pending" else timing["status"]
|
||||||
punch_remarks = _blank_to_none(remarks)
|
punch_remarks = _blank_to_none(remarks)
|
||||||
@@ -1166,6 +1188,7 @@ def punch_out_attendance(
|
|||||||
longitude=longitude,
|
longitude=longitude,
|
||||||
client_ip=client_ip,
|
client_ip=client_ip,
|
||||||
)
|
)
|
||||||
|
_raise_if_required_geo_missing(branch_settings, evaluation)
|
||||||
row.punch_out_utc = now
|
row.punch_out_utc = now
|
||||||
row.punch_out_local_at = local_dt
|
row.punch_out_local_at = local_dt
|
||||||
row.branch_timezone = row.branch_timezone or branch_tz
|
row.branch_timezone = row.branch_timezone or branch_tz
|
||||||
|
|||||||
+9
-6
@@ -26,7 +26,7 @@
|
|||||||
Click Punch In/Punch Out. The app will ask for browser location before submitting attendance.
|
Click Punch In/Punch Out. The app will ask for browser location before submitting attendance.
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
<div class="mt-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||||
If branch geofence/IP verification is not configured, attendance may be approved without location check. Configure branch attendance geo/IP settings to enforce verification.
|
When branch geofence is enabled, browser location is mandatory. If location is denied/unavailable, attendance will not be submitted.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-1">
|
<div class="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-1">
|
||||||
@@ -61,8 +61,7 @@
|
|||||||
Click Continue, then choose <strong>Allow</strong> in the browser location popup.
|
Click Continue, then choose <strong>Allow</strong> in the browser location popup.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-3 text-xs leading-5 text-slate-500">
|
<p class="mt-3 text-xs leading-5 text-slate-500">
|
||||||
If you are outside the branch or location is denied/unavailable, your attendance can still be saved.
|
If you are outside the branch, attendance will require approval. If browser location is denied/unavailable while branch geofence is enabled, attendance will not be submitted.
|
||||||
It will require approval only when branch geofence/IP verification is configured.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +133,7 @@ window.EmployeePortalGeo = window.EmployeePortalGeo || (function () {
|
|||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
if (!navigator.geolocation) {
|
if (!navigator.geolocation) {
|
||||||
setHidden(form, '', '', '');
|
setHidden(form, '', '', '');
|
||||||
updateStatus('Browser geolocation is not available. Attendance can be saved, but geofence verification cannot be performed.', 'warn');
|
updateStatus('Browser geolocation is not available. Attendance was not submitted. Enable browser/device location and try again.', 'warn');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,7 +149,7 @@ window.EmployeePortalGeo = window.EmployeePortalGeo || (function () {
|
|||||||
if (err && err.code === 1) reason = 'Location permission denied. Allow location for this site in browser settings.';
|
if (err && err.code === 1) reason = 'Location permission denied. Allow location for this site in browser settings.';
|
||||||
if (err && err.code === 2) reason = 'Location unavailable. Enable device location service and try again.';
|
if (err && err.code === 2) reason = 'Location unavailable. Enable device location service and try again.';
|
||||||
if (err && err.code === 3) reason = 'Location capture timed out. Try again with stable GPS/location service.';
|
if (err && err.code === 3) reason = 'Location capture timed out. Try again with stable GPS/location service.';
|
||||||
updateStatus(reason + ' Attendance can be saved, but geofence verification cannot be performed.', 'warn');
|
updateStatus(reason + ' Attendance was not submitted. Please allow browser location/GPS and try again.', 'warn');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 });
|
}, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 });
|
||||||
});
|
});
|
||||||
@@ -167,7 +166,11 @@ window.EmployeePortalGeo = window.EmployeePortalGeo || (function () {
|
|||||||
var proceed = await openLocationModal();
|
var proceed = await openLocationModal();
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
await captureLocation(form);
|
var captured = await captureLocation(form);
|
||||||
|
if (!captured) {
|
||||||
|
updateStatus('Attendance not submitted because location was not captured.', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
form.dataset.geoSubmitted = '1';
|
form.dataset.geoSubmitted = '1';
|
||||||
form.submit();
|
form.submit();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
Click Continue, then choose <span class="font-semibold text-slate-900">Allow</span> in the browser location popup.
|
Click Continue, then choose <span class="font-semibold text-slate-900">Allow</span> in the browser location popup.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-xs text-slate-500">
|
<p class="mt-2 text-xs text-slate-500">
|
||||||
If you are outside the branch or location is denied/unavailable, your attendance can still be saved. It will require approval only when branch geofence/IP verification is configured.
|
If you are outside the branch, attendance will require approval. If browser location is denied/unavailable while branch geofence is enabled, attendance will not be submitted.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
geoAttempted = true;
|
geoAttempted = true;
|
||||||
if (!navigator.geolocation) {
|
if (!navigator.geolocation) {
|
||||||
setHidden('', '', '');
|
setHidden('', '', '');
|
||||||
updateStatus('Browser geolocation is not available. Attendance can be saved, but geofence verification cannot be performed if location is denied/unavailable.', 'warn');
|
updateStatus('Browser geolocation is not available. Attendance was not submitted. Enable browser/device location and try again.', 'warn');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
if (err && err.code === 1) reason = 'Location permission denied. Please allow location for this site in browser settings.';
|
if (err && err.code === 1) reason = 'Location permission denied. Please allow location for this site in browser settings.';
|
||||||
if (err && err.code === 2) reason = 'Location unavailable. Please enable device GPS/location service and try again.';
|
if (err && err.code === 2) reason = 'Location unavailable. Please enable device GPS/location service and try again.';
|
||||||
if (err && err.code === 3) reason = 'Location capture timed out. Please try again near a window or with GPS enabled.';
|
if (err && err.code === 3) reason = 'Location capture timed out. Please try again near a window or with GPS enabled.';
|
||||||
updateStatus(reason + ' Attendance can be saved, but geofence verification cannot be performed if location is denied/unavailable.', 'warn');
|
updateStatus(reason + ' Attendance was not submitted. Please allow browser location/GPS and try again.', 'warn');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 });
|
}, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 });
|
||||||
});
|
});
|
||||||
@@ -238,7 +238,11 @@
|
|||||||
const proceed = await openLocationModal(form);
|
const proceed = await openLocationModal(form);
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
await captureLocation();
|
const captured = await captureLocation();
|
||||||
|
if (!captured) {
|
||||||
|
updateStatus('Attendance not submitted because location was not captured.', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
form.submit();
|
form.submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user