diff --git a/app/modules/employees/service.py b/app/modules/employees/service.py index 4c73ab6..ac3b9b3 100644 --- a/app/modules/employees/service.py +++ b/app/modules/employees/service.py @@ -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" +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( db: Session, *, @@ -1071,6 +1092,7 @@ def punch_in_attendance( longitude=longitude, client_ip=client_ip, ) + _raise_if_required_geo_missing(branch_settings, evaluation) timing = _evaluate_attendance_timing(branch, branch_settings, local_dt) final_status = evaluation["status"] if evaluation["approval_status"] == "pending" else timing["status"] punch_remarks = _blank_to_none(remarks) @@ -1166,6 +1188,7 @@ def punch_out_attendance( longitude=longitude, client_ip=client_ip, ) + _raise_if_required_geo_missing(branch_settings, evaluation) row.punch_out_utc = now row.punch_out_local_at = local_dt row.branch_timezone = row.branch_timezone or branch_tz diff --git a/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html index d36e29c..0c5e560 100644 --- a/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html +++ b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html @@ -26,7 +26,7 @@ Click Punch In/Punch Out. The app will ask for browser location before submitting attendance.
- 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.
@@ -61,8 +61,7 @@ Click Continue, then choose Allow in the browser location popup.

- 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.

@@ -134,7 +133,7 @@ window.EmployeePortalGeo = window.EmployeePortalGeo || (function () { return new Promise(function (resolve) { if (!navigator.geolocation) { 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); 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 === 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.'; - 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); }, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 }); }); @@ -167,7 +166,11 @@ window.EmployeePortalGeo = window.EmployeePortalGeo || (function () { var proceed = await openLocationModal(); 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.submit(); }); diff --git a/app/modules/employees/templates/employees/self_attendance.html b/app/modules/employees/templates/employees/self_attendance.html index 9e11066..dd96e63 100644 --- a/app/modules/employees/templates/employees/self_attendance.html +++ b/app/modules/employees/templates/employees/self_attendance.html @@ -115,7 +115,7 @@ Click Continue, then choose Allow in the browser location popup.

- 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.

@@ -200,7 +200,7 @@ geoAttempted = true; if (!navigator.geolocation) { 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); 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 === 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.'; - 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); }, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 }); }); @@ -238,7 +238,11 @@ const proceed = await openLocationModal(form); if (!proceed) return; - await captureLocation(); + const captured = await captureLocation(); + if (!captured) { + updateStatus('Attendance not submitted because location was not captured.', 'warn'); + return; + } form.submit(); } });