From c16632487db73f293caf06bf2e54eb12dbe55399 Mon Sep 17 00:00:00 2001 From: Nguyen Ngo Date: Fri, 1 Aug 2025 16:51:16 -0400 Subject: [PATCH] Update location function --- app.py | 174 +++++++++--- location_debug_script.py | 529 ++++++++++++++++++++++++++++++++++++ static/js/qr_destination.js | 198 +++++++++----- 3 files changed, 797 insertions(+), 104 deletions(-) create mode 100644 location_debug_script.py diff --git a/app.py b/app.py index 00ae156..ff858b7 100644 --- a/app.py +++ b/app.py @@ -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//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, diff --git a/location_debug_script.py b/location_debug_script.py new file mode 100644 index 0000000..3eae5f9 --- /dev/null +++ b/location_debug_script.py @@ -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//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() \ No newline at end of file diff --git a/static/js/qr_destination.js b/static/js/qr_destination.js index 62e94af..6cd6bfc 100644 --- a/static/js/qr_destination.js +++ b/static/js/qr_destination.js @@ -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); }); }