changes
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
@@ -54,6 +55,19 @@ class FusionClockLocation(models.Model):
|
||||
default=lambda self: self.env.user.tz or 'UTC',
|
||||
)
|
||||
|
||||
# IP whitelist fallback
|
||||
ip_whitelist = fields.Text(
|
||||
string='IP Whitelist',
|
||||
help="One IP address or CIDR per line. Used as fallback when GPS is unavailable.",
|
||||
)
|
||||
|
||||
# Photo verification
|
||||
require_photo = fields.Boolean(
|
||||
string='Require Photo on Clock-In',
|
||||
default=False,
|
||||
help="If enabled, employees must take a selfie when clocking in at this location.",
|
||||
)
|
||||
|
||||
# Computed
|
||||
attendance_count = fields.Integer(
|
||||
string='Total Attendances',
|
||||
@@ -89,6 +103,28 @@ class FusionClockLocation(models.Model):
|
||||
('x_fclk_location_id', '=', rec.id),
|
||||
])
|
||||
|
||||
def check_ip_whitelist(self, client_ip):
|
||||
"""Check if a client IP matches this location's whitelist.
|
||||
Returns True if matched, False otherwise.
|
||||
"""
|
||||
if not self.ip_whitelist or not client_ip:
|
||||
return False
|
||||
try:
|
||||
client = ipaddress.ip_address(client_ip)
|
||||
for line in self.ip_whitelist.strip().split('\n'):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
try:
|
||||
network = ipaddress.ip_network(line, strict=False)
|
||||
if client in network:
|
||||
return True
|
||||
except ValueError:
|
||||
continue
|
||||
except ValueError:
|
||||
return False
|
||||
return False
|
||||
|
||||
def action_geocode_address(self):
|
||||
"""Geocode the address to get lat/lng using Google Geocoding API.
|
||||
Falls back to Nominatim (OpenStreetMap) if Google fails.
|
||||
@@ -97,7 +133,6 @@ class FusionClockLocation(models.Model):
|
||||
if not self.address:
|
||||
raise UserError(_("Please enter an address first."))
|
||||
|
||||
# Try Google first
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('fusion_clock.google_maps_api_key', '')
|
||||
if api_key:
|
||||
try:
|
||||
@@ -126,13 +161,12 @@ class FusionClockLocation(models.Model):
|
||||
},
|
||||
}
|
||||
elif data.get('status') == 'REQUEST_DENIED':
|
||||
_logger.warning("Google Geocoding API denied. Enable the Geocoding API in Google Cloud Console. Falling back to Nominatim.")
|
||||
_logger.warning("Google Geocoding API denied. Falling back to Nominatim.")
|
||||
else:
|
||||
_logger.warning("Google geocoding returned: %s. Trying Nominatim fallback.", data.get('status'))
|
||||
_logger.warning("Google geocoding returned: %s. Trying Nominatim.", data.get('status'))
|
||||
except requests.exceptions.RequestException as e:
|
||||
_logger.warning("Google geocoding network error: %s. Trying Nominatim fallback.", e)
|
||||
_logger.warning("Google geocoding network error: %s. Trying Nominatim.", e)
|
||||
|
||||
# Fallback: Nominatim (OpenStreetMap) - free, no API key needed
|
||||
try:
|
||||
url = 'https://nominatim.openstreetmap.org/search'
|
||||
params = {
|
||||
@@ -165,7 +199,7 @@ class FusionClockLocation(models.Model):
|
||||
},
|
||||
}
|
||||
else:
|
||||
raise UserError(_("Could not geocode address. No results found. Try a more specific address."))
|
||||
raise UserError(_("Could not geocode address. No results found."))
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise UserError(_("Network error during geocoding: %s") % str(e))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user