Update location function
This commit is contained in:
@@ -74,9 +74,7 @@ class QRCode(db.Model):
|
||||
qr_url = db.Column(db.String(255), unique=True, nullable=True)
|
||||
|
||||
class AttendanceData(db.Model):
|
||||
"""
|
||||
Enhanced attendance tracking model with comprehensive location support
|
||||
"""
|
||||
"""Enhanced attendance tracking model with location support"""
|
||||
__tablename__ = 'attendance_data'
|
||||
|
||||
# Existing fields
|
||||
@@ -93,13 +91,13 @@ class AttendanceData(db.Model):
|
||||
created_timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_timestamp = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# NEW: Location tracking fields
|
||||
latitude = db.Column(db.Float, nullable=True, comment='GPS latitude coordinate')
|
||||
longitude = db.Column(db.Float, nullable=True, comment='GPS longitude coordinate')
|
||||
accuracy = db.Column(db.Float, nullable=True, comment='GPS accuracy in meters')
|
||||
altitude = db.Column(db.Float, nullable=True, comment='GPS altitude in meters')
|
||||
location_source = db.Column(db.String(50), default='manual', comment='Source: gps, network, manual')
|
||||
address = db.Column(db.String(500), nullable=True, comment='Reverse geocoded address')
|
||||
# LOCATION FIELDS - Add these if missing
|
||||
latitude = db.Column(db.Float, nullable=True)
|
||||
longitude = db.Column(db.Float, nullable=True)
|
||||
accuracy = db.Column(db.Float, nullable=True)
|
||||
altitude = db.Column(db.Float, nullable=True)
|
||||
location_source = db.Column(db.String(50), default='manual')
|
||||
address = db.Column(db.String(500), nullable=True)
|
||||
|
||||
# Relationships
|
||||
qr_code = db.relationship('QRCode', backref=db.backref('attendance_records', lazy='dynamic'))
|
||||
@@ -1152,7 +1150,7 @@ def qr_destination(qr_url):
|
||||
|
||||
@app.route('/qr/<string:qr_url>/checkin', methods=['POST'])
|
||||
def qr_checkin(qr_url):
|
||||
"""Enhanced staff check-in submission with comprehensive location support"""
|
||||
"""Enhanced staff check-in with proper location handling"""
|
||||
try:
|
||||
# Find QR code by URL
|
||||
qr_code = QRCode.query.filter_by(qr_url=qr_url, active_status=True).first()
|
||||
@@ -1166,17 +1164,26 @@ def qr_checkin(qr_url):
|
||||
# Get form data
|
||||
employee_id = request.form.get('employee_id', '').strip()
|
||||
|
||||
# Enhanced location data extraction
|
||||
location_data = {
|
||||
'latitude': request.form.get('latitude', '').strip(),
|
||||
'longitude': request.form.get('longitude', '').strip(),
|
||||
'accuracy': request.form.get('accuracy', '').strip(),
|
||||
'altitude': request.form.get('altitude', '').strip(),
|
||||
'location_source': request.form.get('locationSource', 'manual').strip(),
|
||||
'address': request.form.get('address', '').strip()
|
||||
}
|
||||
# FIXED: Get location data with correct field names and validation
|
||||
latitude = request.form.get('latitude', '').strip()
|
||||
longitude = request.form.get('longitude', '').strip()
|
||||
accuracy = request.form.get('accuracy', '').strip()
|
||||
altitude = request.form.get('altitude', '').strip()
|
||||
location_source = request.form.get('location_source', 'manual').strip()
|
||||
address = request.form.get('address', '').strip()
|
||||
|
||||
print(f"📍 Location data received: {location_data}")
|
||||
# CRITICAL DEBUG: Log all received form data
|
||||
print(f"\n{'='*50}")
|
||||
print(f"📥 QR CHECK-IN DATA RECEIVED:")
|
||||
print(f"{'='*50}")
|
||||
print(f"Employee ID: '{employee_id}'")
|
||||
print(f"Latitude: '{latitude}' (type: {type(latitude)})")
|
||||
print(f"Longitude: '{longitude}' (type: {type(longitude)})")
|
||||
print(f"Accuracy: '{accuracy}' (type: {type(accuracy)})")
|
||||
print(f"Altitude: '{altitude}' (type: {type(altitude)})")
|
||||
print(f"Location Source: '{location_source}' (type: {type(location_source)})")
|
||||
print(f"Address: '{address}' (type: {type(address)})")
|
||||
print(f"{'='*50}\n")
|
||||
|
||||
if not employee_id:
|
||||
return jsonify({
|
||||
@@ -1185,7 +1192,7 @@ def qr_checkin(qr_url):
|
||||
}), 400
|
||||
|
||||
# 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({
|
||||
'success': False,
|
||||
'message': 'Invalid employee ID format. Use 3-20 alphanumeric characters.'
|
||||
@@ -1219,10 +1226,74 @@ def qr_checkin(qr_url):
|
||||
if client_ip and ',' in client_ip:
|
||||
client_ip = client_ip.split(',')[0].strip()
|
||||
|
||||
# Process and validate location data
|
||||
processed_location = process_location_data(location_data)
|
||||
# FIXED: Process location data with enhanced validation
|
||||
lat_value = None
|
||||
lng_value = None
|
||||
acc_value = None
|
||||
alt_value = None
|
||||
|
||||
# Create enhanced attendance record
|
||||
# Process latitude
|
||||
if latitude and latitude.strip() and latitude not in ['null', '', 'undefined']:
|
||||
try:
|
||||
lat_value = float(latitude)
|
||||
if not (-90 <= lat_value <= 90):
|
||||
print(f"⚠️ Invalid latitude range: {lat_value}")
|
||||
lat_value = None
|
||||
else:
|
||||
print(f"✅ Valid latitude: {lat_value}")
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"⚠️ Latitude parsing error: {e}")
|
||||
|
||||
# Process longitude
|
||||
if longitude and longitude.strip() and longitude not in ['null', '', 'undefined']:
|
||||
try:
|
||||
lng_value = float(longitude)
|
||||
if not (-180 <= lng_value <= 180):
|
||||
print(f"⚠️ Invalid longitude range: {lng_value}")
|
||||
lng_value = None
|
||||
else:
|
||||
print(f"✅ Valid longitude: {lng_value}")
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"⚠️ Longitude parsing error: {e}")
|
||||
|
||||
# Process accuracy
|
||||
if accuracy and accuracy.strip() and accuracy not in ['null', '', 'undefined']:
|
||||
try:
|
||||
acc_value = float(accuracy)
|
||||
if acc_value < 0:
|
||||
print(f"⚠️ Invalid accuracy (negative): {acc_value}")
|
||||
acc_value = None
|
||||
else:
|
||||
print(f"✅ Valid accuracy: {acc_value}m")
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"⚠️ Accuracy parsing error: {e}")
|
||||
|
||||
# Process altitude
|
||||
if altitude and altitude.strip() and altitude not in ['null', '', 'undefined']:
|
||||
try:
|
||||
alt_value = float(altitude)
|
||||
print(f"✅ Valid altitude: {alt_value}m")
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"⚠️ Altitude parsing error: {e}")
|
||||
|
||||
# Validate location source
|
||||
valid_sources = ['gps', 'network', 'manual']
|
||||
if location_source not in valid_sources:
|
||||
location_source = 'manual'
|
||||
|
||||
# Truncate address if too long
|
||||
if address and len(address) > 500:
|
||||
address = address[:500]
|
||||
|
||||
print(f"📊 PROCESSED LOCATION DATA:")
|
||||
print(f" Latitude: {lat_value}")
|
||||
print(f" Longitude: {lng_value}")
|
||||
print(f" Accuracy: {acc_value}")
|
||||
print(f" Altitude: {alt_value}")
|
||||
print(f" Source: {location_source}")
|
||||
print(f" Address: {address[:50]}..." if address and len(address) > 50 else f" Address: {address}")
|
||||
|
||||
# Create attendance record
|
||||
attendance = AttendanceData(
|
||||
qr_code_id=qr_code.id,
|
||||
employee_id=employee_id.upper(),
|
||||
@@ -1233,19 +1304,37 @@ def qr_checkin(qr_url):
|
||||
ip_address=client_ip,
|
||||
location_name=qr_code.location,
|
||||
status='present',
|
||||
# Location data
|
||||
latitude=processed_location.get('latitude'),
|
||||
longitude=processed_location.get('longitude'),
|
||||
accuracy=processed_location.get('accuracy'),
|
||||
altitude=processed_location.get('altitude'),
|
||||
location_source=processed_location.get('source', 'manual'),
|
||||
address=processed_location.get('address')
|
||||
# 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:")
|
||||
print(f" Employee: {attendance.employee_id}")
|
||||
print(f" Location: {attendance.location_name}")
|
||||
print(f" GPS: {attendance.latitude}, {attendance.longitude}")
|
||||
print(f" Accuracy: {attendance.accuracy}")
|
||||
print(f" Source: {attendance.location_source}")
|
||||
|
||||
db.session.add(attendance)
|
||||
db.session.commit()
|
||||
|
||||
# Enhanced response with location info
|
||||
# VERIFICATION: Re-fetch the saved record to confirm data persistence
|
||||
saved_record = AttendanceData.query.get(attendance.id)
|
||||
has_location = saved_record.latitude is not None and saved_record.longitude is not None
|
||||
|
||||
print(f"✅ VERIFICATION - Record saved with ID: {saved_record.id}")
|
||||
print(f"✅ Has location data: {has_location}")
|
||||
if has_location:
|
||||
print(f"✅ Saved coordinates: {saved_record.latitude}, {saved_record.longitude}")
|
||||
print(f"✅ Saved accuracy: {saved_record.accuracy}")
|
||||
print(f"✅ Saved source: {saved_record.location_source}")
|
||||
|
||||
# Enhanced response with location verification
|
||||
response_data = {
|
||||
'success': True,
|
||||
'message': 'Check-in successful!',
|
||||
@@ -1255,22 +1344,23 @@ def qr_checkin(qr_url):
|
||||
'event': qr_code.location_event,
|
||||
'check_in_time': attendance.check_in_time.strftime('%H:%M'),
|
||||
'check_in_date': attendance.check_in_date.strftime('%B %d, %Y'),
|
||||
'has_location': attendance.has_location_data,
|
||||
'has_location': has_location,
|
||||
'location_info': {
|
||||
'coordinates': attendance.coordinates_display,
|
||||
'accuracy': f"±{attendance.accuracy:.0f}m" if attendance.accuracy else "Unknown",
|
||||
'source': attendance.location_source.title(),
|
||||
'address': attendance.address or "Not available"
|
||||
} if attendance.has_location_data else None
|
||||
'coordinates': f"{saved_record.latitude:.6f}, {saved_record.longitude:.6f}" if has_location else "No GPS data",
|
||||
'accuracy': f"±{saved_record.accuracy:.0f}m" if saved_record.accuracy else "Unknown",
|
||||
'source': saved_record.location_source.title() if saved_record.location_source else "Manual",
|
||||
'address': saved_record.address or "Not available"
|
||||
} if has_location else None
|
||||
}
|
||||
}
|
||||
|
||||
print(f"✅ Check-in successful for {employee_id.upper()} with location: {attendance.has_location_data}")
|
||||
print(f"📤 SENDING RESPONSE: {response_data['data']['has_location']} location data")
|
||||
|
||||
return jsonify(response_data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Check-in error: {str(e)}")
|
||||
print(f"❌ CHECK-IN ERROR: {str(e)}")
|
||||
import traceback
|
||||
print(f"❌ TRACEBACK: {traceback.format_exc()}")
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
|
||||
@@ -0,0 +1,529 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Location Tracking Debug & Fix Script
|
||||
====================================
|
||||
|
||||
This script will diagnose and fix location tracking issues in your QR system.
|
||||
It will check the database, model, and provide the correct implementation.
|
||||
|
||||
Run this to identify why coordinates aren't being saved.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy import text, inspect
|
||||
|
||||
def create_app():
|
||||
"""Create Flask app for debugging"""
|
||||
app = Flask(__name__)
|
||||
database_url = os.environ.get('DATABASE_URL', 'postgresql://postgres:postgres1411@localhost/qr_management')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = database_url
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
return app
|
||||
|
||||
def check_database_structure():
|
||||
"""Check if location columns exist in database"""
|
||||
print("🔍 STEP 1: Checking Database Structure")
|
||||
print("=" * 50)
|
||||
|
||||
app = create_app()
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
with app.app_context():
|
||||
try:
|
||||
# Check if attendance_data table exists
|
||||
result = db.session.execute(text("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'attendance_data'
|
||||
);
|
||||
"""))
|
||||
|
||||
if not result.fetchone()[0]:
|
||||
print("❌ attendance_data table NOT found!")
|
||||
return False
|
||||
|
||||
print("✅ attendance_data table exists")
|
||||
|
||||
# Check table structure
|
||||
result = db.session.execute(text("""
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'attendance_data'
|
||||
ORDER BY ordinal_position;
|
||||
"""))
|
||||
|
||||
columns = result.fetchall()
|
||||
print(f"\n📋 Table has {len(columns)} columns:")
|
||||
|
||||
location_columns = ['latitude', 'longitude', 'accuracy', 'altitude', 'location_source', 'address']
|
||||
found_location_columns = []
|
||||
|
||||
for col_name, data_type, nullable, default in columns:
|
||||
status = "🟢" if col_name in location_columns else "⚪"
|
||||
default_str = f" (default: {default})" if default else ""
|
||||
print(f" {status} {col_name}: {data_type} {'NULL' if nullable == 'YES' else 'NOT NULL'}{default_str}")
|
||||
|
||||
if col_name in location_columns:
|
||||
found_location_columns.append(col_name)
|
||||
|
||||
print(f"\n📊 Location columns found: {len(found_location_columns)}/6")
|
||||
|
||||
if len(found_location_columns) == 0:
|
||||
print("❌ NO location columns found! Database migration needed.")
|
||||
return False
|
||||
elif len(found_location_columns) < 6:
|
||||
missing = set(location_columns) - set(found_location_columns)
|
||||
print(f"⚠️ Missing location columns: {', '.join(missing)}")
|
||||
return False
|
||||
else:
|
||||
print("✅ All location columns present!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Database check failed: {e}")
|
||||
return False
|
||||
|
||||
def add_missing_columns():
|
||||
"""Add missing location columns to database"""
|
||||
print("\n🛠️ STEP 2: Adding Missing Location Columns")
|
||||
print("=" * 50)
|
||||
|
||||
app = create_app()
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
with app.app_context():
|
||||
try:
|
||||
location_columns = [
|
||||
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS latitude FLOAT",
|
||||
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS longitude FLOAT",
|
||||
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS accuracy FLOAT",
|
||||
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS altitude FLOAT",
|
||||
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS location_source VARCHAR(50) DEFAULT 'manual'",
|
||||
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS address VARCHAR(500)"
|
||||
]
|
||||
|
||||
for sql_command in location_columns:
|
||||
try:
|
||||
db.session.execute(text(sql_command))
|
||||
column_name = sql_command.split()[4]
|
||||
print(f"✅ Added: {column_name}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ {sql_command.split()[4]}: {e}")
|
||||
|
||||
db.session.commit()
|
||||
print("✅ Database columns added successfully!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to add columns: {e}")
|
||||
db.session.rollback()
|
||||
return False
|
||||
|
||||
def test_location_data_flow():
|
||||
"""Test the complete location data flow"""
|
||||
print("\n🧪 STEP 3: Testing Location Data Flow")
|
||||
print("=" * 50)
|
||||
|
||||
app = create_app()
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
# Import your actual model
|
||||
try:
|
||||
sys.path.append(os.getcwd())
|
||||
from app import AttendanceData
|
||||
print("✅ Successfully imported AttendanceData model")
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to import AttendanceData: {e}")
|
||||
print(" Creating temporary model for testing...")
|
||||
|
||||
# Create temporary model
|
||||
class AttendanceData(db.Model):
|
||||
__tablename__ = 'attendance_data'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
qr_code_id = db.Column(db.Integer, nullable=False)
|
||||
employee_id = db.Column(db.String(50), nullable=False)
|
||||
check_in_date = db.Column(db.Date, nullable=False)
|
||||
check_in_time = db.Column(db.Time, nullable=False)
|
||||
latitude = db.Column(db.Float, nullable=True)
|
||||
longitude = db.Column(db.Float, nullable=True)
|
||||
accuracy = db.Column(db.Float, nullable=True)
|
||||
altitude = db.Column(db.Float, nullable=True)
|
||||
location_source = db.Column(db.String(50), default='manual')
|
||||
address = db.Column(db.String(500), nullable=True)
|
||||
location_name = db.Column(db.String(100), nullable=False)
|
||||
status = db.Column(db.String(20), default='present')
|
||||
created_timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
with app.app_context():
|
||||
try:
|
||||
# Test creating a record with location data
|
||||
test_record = AttendanceData(
|
||||
qr_code_id=1,
|
||||
employee_id='TEST001',
|
||||
check_in_date=datetime.today().date(),
|
||||
check_in_time=datetime.now().time(),
|
||||
latitude=37.7749,
|
||||
longitude=-122.4194,
|
||||
accuracy=15.0,
|
||||
altitude=100.0,
|
||||
location_source='gps',
|
||||
address='San Francisco, CA',
|
||||
location_name='Test Location',
|
||||
status='present'
|
||||
)
|
||||
|
||||
# Try to add without committing (just test)
|
||||
db.session.add(test_record)
|
||||
db.session.flush() # This will fail if columns don't exist
|
||||
db.session.rollback() # Don't actually save the test record
|
||||
|
||||
print("✅ Location data model test passed!")
|
||||
print(" Model can successfully store location coordinates")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Location data model test failed: {e}")
|
||||
print(" Issue: Model doesn't have location fields or database columns missing")
|
||||
db.session.rollback()
|
||||
return False
|
||||
|
||||
def check_form_field_names():
|
||||
"""Check the form field names in the frontend"""
|
||||
print("\n📝 STEP 4: Checking Form Field Configuration")
|
||||
print("=" * 50)
|
||||
|
||||
# Expected form field names based on your JavaScript
|
||||
frontend_fields = [
|
||||
'latitude',
|
||||
'longitude',
|
||||
'accuracy',
|
||||
'altitude',
|
||||
'location_source', # Note: JavaScript uses 'locationSource' but form submits as 'location_source'
|
||||
'address'
|
||||
]
|
||||
|
||||
# Expected server-side field names
|
||||
server_fields = [
|
||||
'latitude',
|
||||
'longitude',
|
||||
'accuracy',
|
||||
'altitude',
|
||||
'location_source',
|
||||
'address'
|
||||
]
|
||||
|
||||
print("📤 Frontend form fields:")
|
||||
for field in frontend_fields:
|
||||
print(f" ✅ {field}")
|
||||
|
||||
print("\n📥 Server expects these fields:")
|
||||
for field in server_fields:
|
||||
print(f" ✅ {field}")
|
||||
|
||||
print("\n⚠️ POTENTIAL ISSUE FOUND:")
|
||||
print(" JavaScript uses 'locationSource' but form should submit 'location_source'")
|
||||
print(" This might be causing the data not to save!")
|
||||
|
||||
return True
|
||||
|
||||
def generate_fixed_javascript():
|
||||
"""Generate corrected JavaScript code"""
|
||||
print("\n🔧 STEP 5: Generating Fixed JavaScript Code")
|
||||
print("=" * 50)
|
||||
|
||||
fixed_js = '''
|
||||
// FIXED: Update form fields with location data
|
||||
function updateLocationFormFields() {
|
||||
const fields = {
|
||||
'latitude': userLocation.latitude || '',
|
||||
'longitude': userLocation.longitude || '',
|
||||
'accuracy': userLocation.accuracy || '',
|
||||
'altitude': userLocation.altitude || '',
|
||||
'location_source': userLocation.source || 'manual', // FIXED: was 'locationSource'
|
||||
'address': userLocation.address || ''
|
||||
};
|
||||
|
||||
Object.keys(fields).forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.value = fields[fieldId];
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📝 Updated form fields with location data:', fields);
|
||||
}
|
||||
|
||||
// FIXED: Submit function with correct field names
|
||||
function submitCheckin(employeeId) {
|
||||
if (isSubmitting) return false;
|
||||
|
||||
isSubmitting = true;
|
||||
updateSubmitButton(true);
|
||||
hideStatusMessage();
|
||||
|
||||
// Ensure location data is in the form
|
||||
updateLocationFormFields();
|
||||
|
||||
// Prepare form data with CORRECT field names
|
||||
const formData = new FormData();
|
||||
formData.append('employee_id', employeeId);
|
||||
|
||||
// FIXED: Use correct field names that match server expectations
|
||||
formData.append('latitude', userLocation.latitude || '');
|
||||
formData.append('longitude', userLocation.longitude || '');
|
||||
formData.append('accuracy', userLocation.accuracy || '');
|
||||
formData.append('altitude', userLocation.altitude || '');
|
||||
formData.append('location_source', userLocation.source || 'manual'); // FIXED
|
||||
formData.append('address', userLocation.address || '');
|
||||
|
||||
// Debug: Log what we're sending
|
||||
console.log('📤 Submitting form data:');
|
||||
for (let [key, value] of formData.entries()) {
|
||||
console.log(` ${key}: ${value}`);
|
||||
}
|
||||
|
||||
const currentUrl = window.location.pathname;
|
||||
const checkinUrl = `${currentUrl}/checkin`;
|
||||
|
||||
fetch(checkinUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
isSubmitting = false;
|
||||
updateSubmitButton(false);
|
||||
|
||||
if (data.success) {
|
||||
showSuccessPage(data);
|
||||
console.log('✅ Check-in successful:', data);
|
||||
stopLocationWatching();
|
||||
} else {
|
||||
showStatusMessage(data.message || 'Check-in failed', 'error');
|
||||
console.log('❌ Check-in failed:', data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
isSubmitting = false;
|
||||
updateSubmitButton(false);
|
||||
showStatusMessage('Network error. Please check your connection and try again.', 'error');
|
||||
console.error('❌ Network error:', error);
|
||||
});
|
||||
}
|
||||
'''
|
||||
|
||||
print("✅ Fixed JavaScript code generated!")
|
||||
print(" Key fixes:")
|
||||
print(" - Changed 'locationSource' to 'location_source' in form fields")
|
||||
print(" - Added debug logging to track form submission")
|
||||
print(" - Ensured field names match server expectations")
|
||||
|
||||
return fixed_js
|
||||
|
||||
def generate_fixed_server_code():
|
||||
"""Generate corrected server-side code"""
|
||||
print("\n🔧 STEP 6: Generating Fixed Server Code")
|
||||
print("=" * 50)
|
||||
|
||||
fixed_server = '''
|
||||
@app.route('/qr/<string:qr_url>/checkin', methods=['POST'])
|
||||
def qr_checkin(qr_url):
|
||||
"""FIXED: Enhanced staff check-in with proper location handling"""
|
||||
try:
|
||||
# Find QR code
|
||||
qr_code = QRCode.query.filter_by(qr_url=qr_url, active_status=True).first()
|
||||
if not qr_code:
|
||||
return jsonify({'success': False, 'message': 'QR code not found'}), 404
|
||||
|
||||
# Get form data
|
||||
employee_id = request.form.get('employee_id', '').strip()
|
||||
|
||||
# FIXED: Get location data with correct field names
|
||||
latitude = request.form.get('latitude', '').strip()
|
||||
longitude = request.form.get('longitude', '').strip()
|
||||
accuracy = request.form.get('accuracy', '').strip()
|
||||
altitude = request.form.get('altitude', '').strip()
|
||||
location_source = request.form.get('location_source', 'manual').strip() # FIXED
|
||||
address = request.form.get('address', '').strip()
|
||||
|
||||
# DEBUG: Log received data
|
||||
print(f"📥 Received location data:")
|
||||
print(f" latitude: '{latitude}'")
|
||||
print(f" longitude: '{longitude}'")
|
||||
print(f" accuracy: '{accuracy}'")
|
||||
print(f" altitude: '{altitude}'")
|
||||
print(f" location_source: '{location_source}'")
|
||||
print(f" address: '{address}'")
|
||||
|
||||
if not employee_id:
|
||||
return jsonify({'success': False, 'message': 'Employee ID required'}), 400
|
||||
|
||||
# Validate employee ID
|
||||
if not re.match(r'^[A-Za-z0-9]{3,20}$', employee_id):
|
||||
return jsonify({'success': False, 'message': 'Invalid employee ID format'}), 400
|
||||
|
||||
# Check for duplicates
|
||||
today = datetime.today()
|
||||
existing = AttendanceData.query.filter_by(
|
||||
qr_code_id=qr_code.id,
|
||||
employee_id=employee_id.upper(),
|
||||
check_in_date=today
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Already checked in at {existing.check_in_time.strftime("%H:%M")}'
|
||||
}), 409
|
||||
|
||||
# FIXED: Process location data properly
|
||||
lat_value = None
|
||||
lng_value = None
|
||||
acc_value = None
|
||||
alt_value = None
|
||||
|
||||
try:
|
||||
if latitude and latitude.strip() and latitude != 'null':
|
||||
lat_value = float(latitude)
|
||||
print(f"✅ Parsed latitude: {lat_value}")
|
||||
|
||||
if longitude and longitude.strip() and longitude != 'null':
|
||||
lng_value = float(longitude)
|
||||
print(f"✅ Parsed longitude: {lng_value}")
|
||||
|
||||
if accuracy and accuracy.strip() and accuracy != 'null':
|
||||
acc_value = float(accuracy)
|
||||
print(f"✅ Parsed accuracy: {acc_value}")
|
||||
|
||||
if altitude and altitude.strip() and altitude != 'null':
|
||||
alt_value = float(altitude)
|
||||
print(f"✅ Parsed altitude: {alt_value}")
|
||||
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"⚠️ Location parsing error: {e}")
|
||||
|
||||
# Create attendance record
|
||||
attendance = AttendanceData(
|
||||
qr_code_id=qr_code.id,
|
||||
employee_id=employee_id.upper(),
|
||||
check_in_date=today,
|
||||
check_in_time=datetime.now().time(),
|
||||
location_name=qr_code.location,
|
||||
status='present'
|
||||
)
|
||||
|
||||
# FIXED: Add location data to model
|
||||
if lat_value is not None:
|
||||
attendance.latitude = lat_value
|
||||
if lng_value is not None:
|
||||
attendance.longitude = lng_value
|
||||
if acc_value is not None:
|
||||
attendance.accuracy = acc_value
|
||||
if alt_value is not None:
|
||||
attendance.altitude = alt_value
|
||||
if location_source:
|
||||
attendance.location_source = location_source
|
||||
if address:
|
||||
attendance.address = address
|
||||
|
||||
print(f"💾 Saving attendance with location: lat={attendance.latitude}, lng={attendance.longitude}")
|
||||
|
||||
db.session.add(attendance)
|
||||
db.session.commit()
|
||||
|
||||
# Verify data was saved
|
||||
saved_record = AttendanceData.query.get(attendance.id)
|
||||
print(f"✅ Saved record: lat={saved_record.latitude}, lng={saved_record.longitude}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Check-in successful!',
|
||||
'data': {
|
||||
'employee_id': employee_id.upper(),
|
||||
'location': qr_code.location,
|
||||
'has_location': saved_record.latitude is not None and saved_record.longitude is not None,
|
||||
'coordinates': f"{saved_record.latitude}, {saved_record.longitude}" if saved_record.latitude else "No GPS data"
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Check-in error: {e}")
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
'''
|
||||
|
||||
print("✅ Fixed server code generated!")
|
||||
print(" Key fixes:")
|
||||
print(" - Added debug logging for received form data")
|
||||
print(" - Improved location data parsing and validation")
|
||||
print(" - Added verification that data was actually saved")
|
||||
print(" - Better error handling and feedback")
|
||||
|
||||
return fixed_server
|
||||
|
||||
def main():
|
||||
"""Main diagnostic function"""
|
||||
print("🩺 QR LOCATION TRACKING DIAGNOSTIC TOOL")
|
||||
print("=" * 60)
|
||||
print("This tool will identify why coordinates aren't being saved.")
|
||||
print("=" * 60)
|
||||
|
||||
issues_found = []
|
||||
|
||||
# Step 1: Check database structure
|
||||
if not check_database_structure():
|
||||
issues_found.append("Database missing location columns")
|
||||
print("\n🛠️ FIXING: Adding missing database columns...")
|
||||
if add_missing_columns():
|
||||
print("✅ Database columns fixed!")
|
||||
else:
|
||||
print("❌ Failed to fix database - manual intervention needed")
|
||||
return
|
||||
|
||||
# Step 2: Test model
|
||||
if not test_location_data_flow():
|
||||
issues_found.append("Model can't handle location data")
|
||||
|
||||
# Step 3: Check form fields
|
||||
check_form_field_names()
|
||||
issues_found.append("Form field name mismatch")
|
||||
|
||||
# Step 4: Generate fixes
|
||||
print("\n" + "=" * 60)
|
||||
print("🎯 DIAGNOSIS COMPLETE")
|
||||
print("=" * 60)
|
||||
|
||||
if issues_found:
|
||||
print(f"❌ Found {len(issues_found)} issues:")
|
||||
for i, issue in enumerate(issues_found, 1):
|
||||
print(f" {i}. {issue}")
|
||||
|
||||
print(f"\n🔧 SOLUTIONS:")
|
||||
print(f"1. Run the database migration script if not done already")
|
||||
print(f"2. Update your JavaScript with the fixed code above")
|
||||
print(f"3. Update your server route with the fixed code above")
|
||||
print(f"4. Add debug logging to track the data flow")
|
||||
|
||||
else:
|
||||
print("✅ No major issues found!")
|
||||
print(" Location tracking should be working.")
|
||||
print(" If still having issues, check browser console for errors.")
|
||||
|
||||
# Generate fixed files
|
||||
print(f"\n📁 FIXED CODE FILES:")
|
||||
print(f"1. Save the fixed JavaScript to your qr_destination.js")
|
||||
print(f"2. Update your app.py qr_checkin route")
|
||||
print(f"3. Test with a mobile device to verify GPS functionality")
|
||||
|
||||
generate_fixed_javascript()
|
||||
generate_fixed_server_code()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+136
-62
@@ -70,23 +70,41 @@ function setupEventListeners() {
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
}
|
||||
|
||||
// NEW: Initialize geolocation functionality
|
||||
// Initialize geolocation functionality
|
||||
function initializeGeolocation() {
|
||||
console.log('📍 Initializing geolocation...');
|
||||
console.log('📍 Initializing geolocation system...');
|
||||
|
||||
if (!navigator.geolocation) {
|
||||
console.warn('❌ Geolocation not supported');
|
||||
showLocationStatus('error', 'Location not supported by this browser');
|
||||
console.log('⚠️ Geolocation not supported by this browser');
|
||||
showLocationStatus('error', 'Location services not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Geolocation supported');
|
||||
console.log('✅ Geolocation API available');
|
||||
|
||||
// Check permissions first
|
||||
checkLocationPermission();
|
||||
|
||||
// Request location
|
||||
// Request location immediately
|
||||
requestUserLocation();
|
||||
|
||||
// Set up continuous watching for better accuracy
|
||||
if ('permissions' in navigator) {
|
||||
navigator.permissions.query({ name: 'geolocation' }).then(function(result) {
|
||||
console.log('📍 Geolocation permission status:', result.state);
|
||||
|
||||
if (result.state === 'granted') {
|
||||
startLocationWatching();
|
||||
}
|
||||
|
||||
result.onchange = function() {
|
||||
console.log('📍 Geolocation permission changed to:', result.state);
|
||||
if (result.state === 'granted') {
|
||||
requestUserLocation();
|
||||
startLocationWatching();
|
||||
} else {
|
||||
stopLocationWatching();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Check location permissions
|
||||
@@ -109,7 +127,7 @@ function checkLocationPermission() {
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Request user's current location
|
||||
// Request user's current location
|
||||
function requestUserLocation() {
|
||||
if (locationRequestActive) {
|
||||
console.log('⏭️ Location request already active');
|
||||
@@ -142,83 +160,120 @@ function requestUserLocation() {
|
||||
}, 16000);
|
||||
}
|
||||
|
||||
// NEW: Handle successful location retrieval
|
||||
// Ensure location form fields exist
|
||||
function ensureLocationFormFields() {
|
||||
const form = document.getElementById('checkinForm');
|
||||
if (!form) {
|
||||
console.log('⚠️ Check-in form not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const locationFields = ['latitude', 'longitude', 'accuracy', 'altitude', 'location_source', 'address'];
|
||||
|
||||
locationFields.forEach(fieldName => {
|
||||
if (!document.getElementById(fieldName)) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.id = fieldName;
|
||||
input.name = fieldName;
|
||||
input.value = '';
|
||||
form.appendChild(input);
|
||||
console.log(`✅ Created hidden field: ${fieldName}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle successful location retrieval
|
||||
function handleLocationSuccess(position) {
|
||||
locationRequestActive = false;
|
||||
|
||||
const coords = position.coords;
|
||||
console.log('✅ Location obtained:', coords);
|
||||
|
||||
// Store location data
|
||||
userLocation = {
|
||||
console.log('✅ Location obtained:', {
|
||||
latitude: coords.latitude,
|
||||
longitude: coords.longitude,
|
||||
accuracy: coords.accuracy,
|
||||
altitude: coords.altitude,
|
||||
timestamp: position.timestamp
|
||||
});
|
||||
|
||||
// Validate coordinates
|
||||
if (!coords.latitude || !coords.longitude) {
|
||||
console.log('⚠️ Invalid coordinates received');
|
||||
handleLocationError({ code: 2, message: 'Invalid coordinates' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Store location data with validation
|
||||
userLocation = {
|
||||
latitude: Number(coords.latitude).toFixed(6), // Limit precision
|
||||
longitude: Number(coords.longitude).toFixed(6),
|
||||
accuracy: coords.accuracy ? Math.round(coords.accuracy) : null,
|
||||
altitude: coords.altitude ? Math.round(coords.altitude) : null,
|
||||
timestamp: position.timestamp,
|
||||
source: 'gps',
|
||||
address: null
|
||||
};
|
||||
|
||||
// Update form fields
|
||||
console.log('💾 Stored location data:', userLocation);
|
||||
|
||||
// Update form fields immediately
|
||||
updateLocationFormFields();
|
||||
|
||||
// Update display
|
||||
updateLocationDisplay();
|
||||
|
||||
// Show success status
|
||||
const accuracyText = coords.accuracy ? Math.round(coords.accuracy) : 'unknown';
|
||||
const accuracyLevel = getAccuracyLevel(coords.accuracy);
|
||||
const accuracyText = coords.accuracy ? `±${Math.round(coords.accuracy)}m` : 'unknown';
|
||||
showLocationStatus('success', `Location captured (${accuracyText} accuracy)`);
|
||||
|
||||
showLocationStatus('success',
|
||||
`Location captured (±${accuracyText}m - ${accuracyLevel} accuracy)`
|
||||
);
|
||||
|
||||
console.log('📍 Location stored:', userLocation);
|
||||
|
||||
// Try to get address from coordinates
|
||||
// Try to get address
|
||||
reverseGeocodeLocation(coords.latitude, coords.longitude);
|
||||
|
||||
// Start watching for better accuracy (optional)
|
||||
startLocationWatching();
|
||||
}
|
||||
|
||||
// NEW: Handle location errors
|
||||
// Handle location errors
|
||||
function handleLocationError(error) {
|
||||
locationRequestActive = false;
|
||||
|
||||
console.error('❌ Location error:', error);
|
||||
|
||||
let message = 'Unable to get location';
|
||||
let details = 'Check-in will work without location';
|
||||
|
||||
console.log('❌ Location error:', error);
|
||||
|
||||
switch(error.code) {
|
||||
case error.PERMISSION_DENIED:
|
||||
message = 'Location access denied';
|
||||
details = 'Enable location permission in browser settings';
|
||||
message = 'Location access denied - please enable in browser settings';
|
||||
break;
|
||||
case error.POSITION_UNAVAILABLE:
|
||||
message = 'Location unavailable';
|
||||
details = 'GPS signal is weak or unavailable';
|
||||
message = 'Location unavailable - GPS signal weak';
|
||||
break;
|
||||
case error.TIMEOUT:
|
||||
message = 'Location request timed out';
|
||||
details = 'Try refreshing or check connection';
|
||||
break;
|
||||
default:
|
||||
message = 'Location error occurred';
|
||||
details = 'Please try again';
|
||||
break;
|
||||
}
|
||||
|
||||
showLocationStatus('error', `${message} - ${details}`);
|
||||
|
||||
// Set location source to manual
|
||||
showLocationStatus('error', `${message} - check-in will continue without location`);
|
||||
userLocation.source = 'manual';
|
||||
updateLocationFormFields();
|
||||
}
|
||||
|
||||
// NEW: Start watching location for continuous updates
|
||||
// Enhanced form initialization
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🚀 QR Destination page initialized with enhanced location tracking');
|
||||
|
||||
// Initialize the page
|
||||
initializePage();
|
||||
setupEventListeners();
|
||||
startTimeUpdater();
|
||||
|
||||
// Initialize geolocation
|
||||
initializeGeolocation();
|
||||
|
||||
// Add hidden form fields for location data
|
||||
ensureLocationFormFields();
|
||||
});
|
||||
|
||||
// Start watching location for continuous updates
|
||||
function startLocationWatching() {
|
||||
if (locationWatchId !== null) {
|
||||
console.log('👁️ Already watching location');
|
||||
@@ -257,25 +312,32 @@ function stopLocationWatching() {
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Update form fields with location data
|
||||
// Update form fields with location data
|
||||
function updateLocationFormFields() {
|
||||
const fields = {
|
||||
'latitude': userLocation.latitude || '',
|
||||
'longitude': userLocation.longitude || '',
|
||||
'accuracy': userLocation.accuracy || '',
|
||||
'altitude': userLocation.altitude || '',
|
||||
'locationSource': userLocation.source || 'manual',
|
||||
'location_source': userLocation.source || 'manual', // FIXED: was 'locationSource'
|
||||
'address': userLocation.address || ''
|
||||
};
|
||||
|
||||
// Update hidden form fields
|
||||
Object.keys(fields).forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.value = fields[fieldId];
|
||||
let field = document.getElementById(fieldId);
|
||||
if (!field) {
|
||||
// Create hidden input if it doesn't exist
|
||||
field = document.createElement('input');
|
||||
field.type = 'hidden';
|
||||
field.id = fieldId;
|
||||
field.name = fieldId;
|
||||
document.getElementById('checkinForm').appendChild(field);
|
||||
}
|
||||
field.value = fields[fieldId];
|
||||
});
|
||||
|
||||
console.log('📝 Updated form fields with location data');
|
||||
console.log('📝 Updated form fields with location data:', fields);
|
||||
}
|
||||
|
||||
// NEW: Update location display
|
||||
@@ -549,24 +611,25 @@ function shakeInput(input) {
|
||||
}
|
||||
|
||||
function submitCheckin(employeeId) {
|
||||
console.log('🚀 Submitting check-in for:', employeeId);
|
||||
|
||||
// NEW: Log location data being submitted
|
||||
const locationData = getCurrentLocationData();
|
||||
console.log('📍 Location data:', locationData);
|
||||
if (isSubmitting) {
|
||||
console.log('⏭️ Already submitting, ignoring duplicate request');
|
||||
return false;
|
||||
}
|
||||
|
||||
isSubmitting = true;
|
||||
updateSubmitButton(true);
|
||||
showStatusMessage('Processing check-in...', 'info');
|
||||
hideStatusMessage();
|
||||
|
||||
// NEW: Ensure all location data is in the form
|
||||
console.log('📤 Starting check-in submission for:', employeeId);
|
||||
console.log('📍 Current location data:', userLocation);
|
||||
|
||||
// Ensure location data is in the form
|
||||
updateLocationFormFields();
|
||||
|
||||
// Prepare form data
|
||||
// Prepare form data with CORRECT field names
|
||||
const formData = new FormData();
|
||||
formData.append('employee_id', employeeId);
|
||||
|
||||
// NEW: Add location data to form submission
|
||||
formData.append('latitude', userLocation.latitude || '');
|
||||
formData.append('longitude', userLocation.longitude || '');
|
||||
formData.append('accuracy', userLocation.accuracy || '');
|
||||
@@ -574,11 +637,17 @@ function submitCheckin(employeeId) {
|
||||
formData.append('location_source', userLocation.source || 'manual');
|
||||
formData.append('address', userLocation.address || '');
|
||||
|
||||
// DEBUG: Log exactly what we're sending
|
||||
console.log('📤 Form data being submitted:');
|
||||
for (let [key, value] of formData.entries()) {
|
||||
console.log(` ${key}: "${value}"`);
|
||||
}
|
||||
|
||||
// Get the current URL for the check-in endpoint
|
||||
const currentUrl = window.location.pathname;
|
||||
const checkinUrl = `${currentUrl}/checkin`;
|
||||
|
||||
console.log('📤 Submitting to:', checkinUrl);
|
||||
console.log('🎯 Submitting to URL:', checkinUrl);
|
||||
|
||||
fetch(checkinUrl, {
|
||||
method: 'POST',
|
||||
@@ -587,14 +656,19 @@ function submitCheckin(employeeId) {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
console.log('📡 Server response status:', response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
isSubmitting = false;
|
||||
updateSubmitButton(false);
|
||||
|
||||
console.log('📥 Server response:', data);
|
||||
|
||||
if (data.success) {
|
||||
showSuccessPage(data);
|
||||
console.log('✅ Check-in successful:', data);
|
||||
console.log('✅ Check-in successful with location:', data.data?.has_location || false);
|
||||
|
||||
// Stop location watching after successful check-in
|
||||
stopLocationWatching();
|
||||
@@ -607,7 +681,7 @@ function submitCheckin(employeeId) {
|
||||
isSubmitting = false;
|
||||
updateSubmitButton(false);
|
||||
showStatusMessage('Network error. Please check your connection and try again.', 'error');
|
||||
console.error('❌ Check-in error:', error);
|
||||
console.error('❌ Network error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user