Updated location functions
This commit is contained in:
@@ -1150,7 +1150,7 @@ def qr_destination(qr_url):
|
|||||||
|
|
||||||
@app.route('/qr/<string:qr_url>/checkin', methods=['POST'])
|
@app.route('/qr/<string:qr_url>/checkin', methods=['POST'])
|
||||||
def qr_checkin(qr_url):
|
def qr_checkin(qr_url):
|
||||||
"""Enhanced staff check-in with proper location handling"""
|
"""FIXED: Enhanced staff check-in with guaranteed location saving"""
|
||||||
try:
|
try:
|
||||||
# Find QR code by URL
|
# Find QR code by URL
|
||||||
qr_code = QRCode.query.filter_by(qr_url=qr_url, active_status=True).first()
|
qr_code = QRCode.query.filter_by(qr_url=qr_url, active_status=True).first()
|
||||||
@@ -1164,7 +1164,7 @@ def qr_checkin(qr_url):
|
|||||||
# Get form data
|
# Get form data
|
||||||
employee_id = request.form.get('employee_id', '').strip()
|
employee_id = request.form.get('employee_id', '').strip()
|
||||||
|
|
||||||
# FIXED: Get location data with correct field names and validation
|
# CRITICAL: Get location data with debug logging
|
||||||
latitude = request.form.get('latitude', '').strip()
|
latitude = request.form.get('latitude', '').strip()
|
||||||
longitude = request.form.get('longitude', '').strip()
|
longitude = request.form.get('longitude', '').strip()
|
||||||
accuracy = request.form.get('accuracy', '').strip()
|
accuracy = request.form.get('accuracy', '').strip()
|
||||||
@@ -1172,18 +1172,20 @@ def qr_checkin(qr_url):
|
|||||||
location_source = request.form.get('location_source', 'manual').strip()
|
location_source = request.form.get('location_source', 'manual').strip()
|
||||||
address = request.form.get('address', '').strip()
|
address = request.form.get('address', '').strip()
|
||||||
|
|
||||||
# CRITICAL DEBUG: Log all received form data
|
# COMPREHENSIVE DEBUG: Log all received form data
|
||||||
print(f"\n{'='*50}")
|
print(f"\n{'='*60}")
|
||||||
print(f"📥 QR CHECK-IN DATA RECEIVED:")
|
print(f"🔍 QR CHECK-IN DEBUG - FORM DATA RECEIVED")
|
||||||
print(f"{'='*50}")
|
print(f"{'='*60}")
|
||||||
print(f"Employee ID: '{employee_id}'")
|
print(f"Employee ID: '{employee_id}'")
|
||||||
print(f"Latitude: '{latitude}' (type: {type(latitude)})")
|
print(f"Latitude: '{latitude}' (length: {len(latitude)}, type: {type(latitude)})")
|
||||||
print(f"Longitude: '{longitude}' (type: {type(longitude)})")
|
print(f"Longitude: '{longitude}' (length: {len(longitude)}, type: {type(longitude)})")
|
||||||
print(f"Accuracy: '{accuracy}' (type: {type(accuracy)})")
|
print(f"Accuracy: '{accuracy}' (length: {len(accuracy)}, type: {type(accuracy)})")
|
||||||
print(f"Altitude: '{altitude}' (type: {type(altitude)})")
|
print(f"Altitude: '{altitude}' (length: {len(altitude)}, type: {type(altitude)})")
|
||||||
print(f"Location Source: '{location_source}' (type: {type(location_source)})")
|
print(f"Location Source: '{location_source}' (type: {type(location_source)})")
|
||||||
print(f"Address: '{address}' (type: {type(address)})")
|
print(f"Address: '{address}' (length: {len(address) if address else 0})")
|
||||||
print(f"{'='*50}\n")
|
print(f"QR Code ID: {qr_code.id}")
|
||||||
|
print(f"QR Location: {qr_code.location}")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
if not employee_id:
|
if not employee_id:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
@@ -1192,7 +1194,7 @@ def qr_checkin(qr_url):
|
|||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# Validate employee ID format
|
# Validate employee ID format
|
||||||
if not re.match(r'^[A-Za-z0-9]{3,20}', employee_id):
|
if not re.match(r'^[A-Za-z0-9]{3,20}$', employee_id):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Invalid employee ID format. Use 3-20 alphanumeric characters.'
|
'message': 'Invalid employee ID format. Use 3-20 alphanumeric characters.'
|
||||||
@@ -1215,7 +1217,6 @@ def qr_checkin(qr_url):
|
|||||||
# Parse user agent for device info
|
# Parse user agent for device info
|
||||||
user_agent = request.headers.get('User-Agent', '')
|
user_agent = request.headers.get('User-Agent', '')
|
||||||
try:
|
try:
|
||||||
from user_agents import parse
|
|
||||||
parsed_agent = parse(user_agent)
|
parsed_agent = parse(user_agent)
|
||||||
device_info = f"{parsed_agent.browser.family} on {parsed_agent.os.family}"
|
device_info = f"{parsed_agent.browser.family} on {parsed_agent.os.family}"
|
||||||
except:
|
except:
|
||||||
@@ -1226,66 +1227,84 @@ def qr_checkin(qr_url):
|
|||||||
if client_ip and ',' in client_ip:
|
if client_ip and ',' in client_ip:
|
||||||
client_ip = client_ip.split(',')[0].strip()
|
client_ip = client_ip.split(',')[0].strip()
|
||||||
|
|
||||||
# FIXED: Process location data with enhanced validation
|
# CRITICAL FIX: Enhanced location data processing with validation
|
||||||
lat_value = None
|
lat_value = None
|
||||||
lng_value = None
|
lng_value = None
|
||||||
acc_value = None
|
acc_value = None
|
||||||
alt_value = None
|
alt_value = None
|
||||||
|
|
||||||
# Process latitude
|
print(f"🔄 PROCESSING LOCATION DATA:")
|
||||||
if latitude and latitude.strip() and latitude not in ['null', '', 'undefined']:
|
|
||||||
|
# Process latitude with comprehensive validation
|
||||||
|
if latitude and latitude.strip() and latitude not in ['null', '', 'undefined', 'NaN']:
|
||||||
try:
|
try:
|
||||||
lat_value = float(latitude)
|
lat_value = float(latitude)
|
||||||
if not (-90 <= lat_value <= 90):
|
if -90 <= lat_value <= 90:
|
||||||
print(f"⚠️ Invalid latitude range: {lat_value}")
|
print(f"✅ Valid latitude: {lat_value}")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Invalid latitude range: {lat_value} (must be -90 to 90)")
|
||||||
|
lat_value = None
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
print(f"❌ Latitude parsing error: {e}")
|
||||||
lat_value = None
|
lat_value = None
|
||||||
else:
|
else:
|
||||||
print(f"✅ Valid latitude: {lat_value}")
|
print(f"📍 No latitude data: '{latitude}'")
|
||||||
except (ValueError, TypeError) as e:
|
|
||||||
print(f"⚠️ Latitude parsing error: {e}")
|
|
||||||
|
|
||||||
# Process longitude
|
# Process longitude with comprehensive validation
|
||||||
if longitude and longitude.strip() and longitude not in ['null', '', 'undefined']:
|
if longitude and longitude.strip() and longitude not in ['null', '', 'undefined', 'NaN']:
|
||||||
try:
|
try:
|
||||||
lng_value = float(longitude)
|
lng_value = float(longitude)
|
||||||
if not (-180 <= lng_value <= 180):
|
if -180 <= lng_value <= 180:
|
||||||
print(f"⚠️ Invalid longitude range: {lng_value}")
|
print(f"✅ Valid longitude: {lng_value}")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Invalid longitude range: {lng_value} (must be -180 to 180)")
|
||||||
|
lng_value = None
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
print(f"❌ Longitude parsing error: {e}")
|
||||||
lng_value = None
|
lng_value = None
|
||||||
else:
|
else:
|
||||||
print(f"✅ Valid longitude: {lng_value}")
|
print(f"📍 No longitude data: '{longitude}'")
|
||||||
except (ValueError, TypeError) as e:
|
|
||||||
print(f"⚠️ Longitude parsing error: {e}")
|
|
||||||
|
|
||||||
# Process accuracy
|
# Process accuracy
|
||||||
if accuracy and accuracy.strip() and accuracy not in ['null', '', 'undefined']:
|
if accuracy and accuracy.strip() and accuracy not in ['null', '', 'undefined', 'NaN']:
|
||||||
try:
|
try:
|
||||||
acc_value = float(accuracy)
|
acc_value = float(accuracy)
|
||||||
if acc_value < 0:
|
if acc_value >= 0:
|
||||||
|
print(f"✅ Valid accuracy: {acc_value}m")
|
||||||
|
else:
|
||||||
print(f"⚠️ Invalid accuracy (negative): {acc_value}")
|
print(f"⚠️ Invalid accuracy (negative): {acc_value}")
|
||||||
acc_value = None
|
acc_value = None
|
||||||
else:
|
|
||||||
print(f"✅ Valid accuracy: {acc_value}m")
|
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
print(f"⚠️ Accuracy parsing error: {e}")
|
print(f"❌ Accuracy parsing error: {e}")
|
||||||
|
acc_value = None
|
||||||
|
else:
|
||||||
|
print(f"📍 No accuracy data: '{accuracy}'")
|
||||||
|
|
||||||
# Process altitude
|
# Process altitude
|
||||||
if altitude and altitude.strip() and altitude not in ['null', '', 'undefined']:
|
if altitude and altitude.strip() and altitude not in ['null', '', 'undefined', 'NaN']:
|
||||||
try:
|
try:
|
||||||
alt_value = float(altitude)
|
alt_value = float(altitude)
|
||||||
print(f"✅ Valid altitude: {alt_value}m")
|
print(f"✅ Valid altitude: {alt_value}m")
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
print(f"⚠️ Altitude parsing error: {e}")
|
print(f"❌ Altitude parsing error: {e}")
|
||||||
|
alt_value = None
|
||||||
|
else:
|
||||||
|
print(f"📍 No altitude data: '{altitude}'")
|
||||||
|
|
||||||
# Validate location source
|
# Validate and clean location source
|
||||||
valid_sources = ['gps', 'network', 'manual']
|
valid_sources = ['gps', 'network', 'manual']
|
||||||
if location_source not in valid_sources:
|
if location_source not in valid_sources:
|
||||||
|
print(f"⚠️ Invalid location source '{location_source}', defaulting to 'manual'")
|
||||||
location_source = 'manual'
|
location_source = 'manual'
|
||||||
|
else:
|
||||||
|
print(f"✅ Valid location source: {location_source}")
|
||||||
|
|
||||||
# Truncate address if too long
|
# Truncate address if too long
|
||||||
if address and len(address) > 500:
|
if address and len(address) > 500:
|
||||||
address = address[:500]
|
address = address[:500]
|
||||||
|
print(f"⚠️ Address truncated to 500 characters")
|
||||||
|
|
||||||
print(f"📊 PROCESSED LOCATION DATA:")
|
print(f"\n📊 FINAL PROCESSED LOCATION DATA:")
|
||||||
print(f" Latitude: {lat_value}")
|
print(f" Latitude: {lat_value}")
|
||||||
print(f" Longitude: {lng_value}")
|
print(f" Longitude: {lng_value}")
|
||||||
print(f" Accuracy: {acc_value}")
|
print(f" Accuracy: {acc_value}")
|
||||||
@@ -1293,7 +1312,12 @@ def qr_checkin(qr_url):
|
|||||||
print(f" Source: {location_source}")
|
print(f" Source: {location_source}")
|
||||||
print(f" Address: {address[:50]}..." if address and len(address) > 50 else f" Address: {address}")
|
print(f" Address: {address[:50]}..." if address and len(address) > 50 else f" Address: {address}")
|
||||||
|
|
||||||
# Create attendance record
|
has_coordinates = lat_value is not None and lng_value is not None
|
||||||
|
print(f" Has Valid Coordinates: {has_coordinates}")
|
||||||
|
|
||||||
|
# CRITICAL: Create attendance record with explicit location field assignment
|
||||||
|
print(f"\n💾 CREATING ATTENDANCE RECORD:")
|
||||||
|
|
||||||
attendance = AttendanceData(
|
attendance = AttendanceData(
|
||||||
qr_code_id=qr_code.id,
|
qr_code_id=qr_code.id,
|
||||||
employee_id=employee_id.upper(),
|
employee_id=employee_id.upper(),
|
||||||
@@ -1303,71 +1327,170 @@ def qr_checkin(qr_url):
|
|||||||
user_agent=user_agent,
|
user_agent=user_agent,
|
||||||
ip_address=client_ip,
|
ip_address=client_ip,
|
||||||
location_name=qr_code.location,
|
location_name=qr_code.location,
|
||||||
status='present',
|
status='present'
|
||||||
# FIXED: Add location data directly to constructor
|
|
||||||
latitude=lat_value,
|
|
||||||
longitude=lng_value,
|
|
||||||
accuracy=acc_value,
|
|
||||||
altitude=alt_value,
|
|
||||||
location_source=location_source,
|
|
||||||
address=address
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"💾 SAVING ATTENDANCE RECORD:")
|
# EXPLICIT LOCATION FIELD ASSIGNMENT
|
||||||
|
if lat_value is not None:
|
||||||
|
attendance.latitude = lat_value
|
||||||
|
print(f"✅ Set latitude: {attendance.latitude}")
|
||||||
|
|
||||||
|
if lng_value is not None:
|
||||||
|
attendance.longitude = lng_value
|
||||||
|
print(f"✅ Set longitude: {attendance.longitude}")
|
||||||
|
|
||||||
|
if acc_value is not None:
|
||||||
|
attendance.accuracy = acc_value
|
||||||
|
print(f"✅ Set accuracy: {attendance.accuracy}")
|
||||||
|
|
||||||
|
if alt_value is not None:
|
||||||
|
attendance.altitude = alt_value
|
||||||
|
print(f"✅ Set altitude: {attendance.altitude}")
|
||||||
|
|
||||||
|
if location_source:
|
||||||
|
attendance.location_source = location_source
|
||||||
|
print(f"✅ Set location_source: {attendance.location_source}")
|
||||||
|
|
||||||
|
if address:
|
||||||
|
attendance.address = address
|
||||||
|
print(f"✅ Set address: {attendance.address[:50]}...")
|
||||||
|
|
||||||
|
print(f"\n💾 SAVING TO DATABASE:")
|
||||||
|
print(f" Record ID will be generated...")
|
||||||
print(f" Employee: {attendance.employee_id}")
|
print(f" Employee: {attendance.employee_id}")
|
||||||
print(f" Location: {attendance.location_name}")
|
print(f" Location Name: {attendance.location_name}")
|
||||||
print(f" GPS: {attendance.latitude}, {attendance.longitude}")
|
print(f" Coordinates: {attendance.latitude}, {attendance.longitude}")
|
||||||
print(f" Accuracy: {attendance.accuracy}")
|
print(f" Accuracy: {attendance.accuracy}")
|
||||||
print(f" Source: {attendance.location_source}")
|
print(f" Source: {attendance.location_source}")
|
||||||
|
|
||||||
|
# Add to session and commit
|
||||||
db.session.add(attendance)
|
db.session.add(attendance)
|
||||||
|
|
||||||
|
try:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
print(f"✅ Database commit successful!")
|
||||||
|
except Exception as commit_error:
|
||||||
|
print(f"❌ Database commit failed: {commit_error}")
|
||||||
|
db.session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
# VERIFICATION: Re-fetch the saved record to confirm data persistence
|
# VERIFICATION: Re-fetch the saved record to confirm data persistence
|
||||||
|
print(f"\n🔍 VERIFICATION - Re-fetching saved record:")
|
||||||
|
|
||||||
|
try:
|
||||||
saved_record = AttendanceData.query.get(attendance.id)
|
saved_record = AttendanceData.query.get(attendance.id)
|
||||||
has_location = saved_record.latitude is not None and saved_record.longitude is not None
|
if saved_record:
|
||||||
|
print(f"✅ Record found with ID: {saved_record.id}")
|
||||||
print(f"✅ VERIFICATION - Record saved with ID: {saved_record.id}")
|
print(f"✅ Employee ID: {saved_record.employee_id}")
|
||||||
print(f"✅ Has location data: {has_location}")
|
print(f"✅ Saved latitude: {saved_record.latitude}")
|
||||||
if has_location:
|
print(f"✅ Saved longitude: {saved_record.longitude}")
|
||||||
print(f"✅ Saved coordinates: {saved_record.latitude}, {saved_record.longitude}")
|
|
||||||
print(f"✅ Saved accuracy: {saved_record.accuracy}")
|
print(f"✅ Saved accuracy: {saved_record.accuracy}")
|
||||||
print(f"✅ Saved source: {saved_record.location_source}")
|
print(f"✅ Saved altitude: {saved_record.altitude}")
|
||||||
|
print(f"✅ Saved location_source: {saved_record.location_source}")
|
||||||
|
print(f"✅ Saved address: {saved_record.address}")
|
||||||
|
|
||||||
# Enhanced response with location verification
|
has_location_final = saved_record.latitude is not None and saved_record.longitude is not None
|
||||||
|
print(f"✅ Final has_location status: {has_location_final}")
|
||||||
|
|
||||||
|
# DATABASE VERIFICATION QUERY
|
||||||
|
verification_query = f"""
|
||||||
|
SELECT id, employee_id, latitude, longitude, accuracy, altitude, location_source, address
|
||||||
|
FROM attendance_data
|
||||||
|
WHERE id = {saved_record.id}
|
||||||
|
"""
|
||||||
|
print(f"\n🔍 Database verification query:")
|
||||||
|
print(f" {verification_query}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"❌ ERROR: Could not re-fetch saved record!")
|
||||||
|
saved_record = attendance # Fallback to original object
|
||||||
|
|
||||||
|
except Exception as verify_error:
|
||||||
|
print(f"❌ Verification error: {verify_error}")
|
||||||
|
saved_record = attendance # Fallback to original object
|
||||||
|
|
||||||
|
# Build response with verification data
|
||||||
response_data = {
|
response_data = {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': 'Check-in successful!',
|
'message': 'Check-in successful!',
|
||||||
'data': {
|
|
||||||
'employee_id': employee_id.upper(),
|
'employee_id': employee_id.upper(),
|
||||||
'location': qr_code.location,
|
'location': qr_code.location,
|
||||||
'event': qr_code.location_event,
|
'event': qr_code.location_event,
|
||||||
'check_in_time': attendance.check_in_time.strftime('%H:%M'),
|
'time': attendance.check_in_time.strftime('%H:%M'),
|
||||||
'check_in_date': attendance.check_in_date.strftime('%B %d, %Y'),
|
'date': attendance.check_in_date.strftime('%Y-%m-%d'),
|
||||||
'has_location': has_location,
|
'has_location': saved_record.latitude is not None and saved_record.longitude is not None,
|
||||||
'location_info': {
|
'location_coordinates': f"{saved_record.latitude},{saved_record.longitude}" if saved_record.latitude and saved_record.longitude else None,
|
||||||
'coordinates': f"{saved_record.latitude:.6f}, {saved_record.longitude:.6f}" if has_location else "No GPS data",
|
'location_accuracy': saved_record.accuracy,
|
||||||
'accuracy': f"±{saved_record.accuracy:.0f}m" if saved_record.accuracy else "Unknown",
|
'location_address': saved_record.address,
|
||||||
'source': saved_record.location_source.title() if saved_record.location_source else "Manual",
|
'location_source': saved_record.location_source
|
||||||
'address': saved_record.address or "Not available"
|
|
||||||
} if has_location else None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"📤 SENDING RESPONSE: {response_data['data']['has_location']} location data")
|
print(f"\n📤 SENDING RESPONSE:")
|
||||||
|
print(f" Success: {response_data['success']}")
|
||||||
|
print(f" Has Location: {response_data['has_location']}")
|
||||||
|
print(f" Coordinates: {response_data['location_coordinates']}")
|
||||||
|
print(f" Accuracy: {response_data['location_accuracy']}")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
return jsonify(response_data)
|
return jsonify(response_data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ CHECK-IN ERROR: {str(e)}")
|
print(f"\n❌ CRITICAL ERROR in qr_checkin:")
|
||||||
|
print(f"❌ Error type: {type(e).__name__}")
|
||||||
|
print(f"❌ Error message: {str(e)}")
|
||||||
|
|
||||||
|
# Print full traceback for debugging
|
||||||
import traceback
|
import traceback
|
||||||
print(f"❌ TRACEBACK: {traceback.format_exc()}")
|
print(f"❌ Full traceback:")
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Check-in failed. Please try again.',
|
'message': 'Check-in failed due to server error. Please try again.',
|
||||||
'error': str(e) if app.debug else None
|
'error': str(e) if app.debug else None
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# ADDITIONAL DEBUGGING ROUTE (Add this to your app.py for testing)
|
||||||
|
# =====================================================================
|
||||||
|
|
||||||
|
@app.route('/debug/attendance/<int:attendance_id>')
|
||||||
|
@admin_required
|
||||||
|
def debug_attendance(attendance_id):
|
||||||
|
"""Debug route to inspect a specific attendance record"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
record = AttendanceData.query.get_or_404(attendance_id)
|
||||||
|
|
||||||
|
debug_info = {
|
||||||
|
'id': record.id,
|
||||||
|
'employee_id': record.employee_id,
|
||||||
|
'location_name': record.location_name,
|
||||||
|
'check_in_date': record.check_in_date.isoformat(),
|
||||||
|
'check_in_time': record.check_in_time.isoformat(),
|
||||||
|
'latitude': record.latitude,
|
||||||
|
'longitude': record.longitude,
|
||||||
|
'accuracy': record.accuracy,
|
||||||
|
'altitude': record.altitude,
|
||||||
|
'location_source': record.location_source,
|
||||||
|
'address': record.address,
|
||||||
|
'has_coordinates': record.latitude is not None and record.longitude is not None,
|
||||||
|
'created_timestamp': record.created_timestamp.isoformat() if record.created_timestamp else None
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'attendance_record': debug_info
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
def process_location_data(location_data):
|
def process_location_data(location_data):
|
||||||
"""
|
"""
|
||||||
Process and validate location data from form
|
Process and validate location data from form
|
||||||
|
|||||||
+751
-706
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user