diff --git a/routes/auth.py b/routes/auth.py index e2560cd..1434f07 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -141,7 +141,7 @@ def login(): ) flash(f'Welcome back, {user.full_name}!', 'success') - print(f"User {user.username} logged in successfully") + logger_handler.logger.info(f"User {user.username} (ID: {user.id}) logged in successfully") # Redirect to intended page or dashboard next_page = request.args.get('next') @@ -158,11 +158,11 @@ def login(): ) flash('Invalid username or password.', 'error') - print(f"Failed login attempt for username: {username}") + logger_handler.logger.warning(f"Failed login attempt for username: {username}") except Exception as e: logger_handler.log_database_error('user_login', e) - print(f"Login error: {e}") + logger_handler.logger.error(f"Login error for username '{username}': {e}") flash('Login error. Please try again.', 'error') return render_template('login.html') @@ -261,4 +261,4 @@ def profile(): except Exception as e: logger_handler.log_database_error('profile_update', e) flash('Profile update failed. Please try again.', 'error') - return redirect(url_for('dashboard.dashboard')) + return redirect(url_for('dashboard.dashboard')) \ No newline at end of file diff --git a/routes/employees.py b/routes/employees.py index 52dc667..aabfdd6 100644 --- a/routes/employees.py +++ b/routes/employees.py @@ -32,11 +32,7 @@ bp = Blueprint('employees', __name__) def employees(): """Display employee management page with search and pagination""" try: - # Log user accessing employee management - try: - logger_handler.logger.info(f"User {session['username']} accessed employee management list") - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.logger.info(f"User {session['username']} accessed employee management list") # Get search parameters search = request.args.get('search', '').strip() @@ -138,12 +134,12 @@ def create_employee(): db.session.commit() # Log employee creation with project info - try: - project = Project.query.get(contract_id_int) - project_name = project.name if project else f"Project {contract_id_int}" - logger_handler.logger.info(f"Admin user {session['username']} created new employee: {employee_id_int} - {first_name} {last_name} assigned to {project_name}") - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + project = Project.query.get(contract_id_int) + project_name = project.name if project else f"Project {contract_id_int}" + logger_handler.logger.info( + f"User {session['username']} created new employee: " + f"{employee_id_int} - {first_name} {last_name} assigned to {project_name}" + ) flash(f'Employee "{first_name} {last_name}" (ID: {employee_id}) created successfully.', 'success') return redirect(url_for('employees.employees')) @@ -215,14 +211,14 @@ def edit_employee(employee_index): employee.contractId = contract_id_int db.session.commit() - + # Log employee update with project info - try: - project = Project.query.get(contract_id_int) - project_name = project.name if project else f"Project {contract_id_int}" - logger_handler.logger.info(f"Admin user {session['username']} updated employee: {employee_index} - {first_name} {last_name} assigned to {project_name}") - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + project = Project.query.get(contract_id_int) + project_name = project.name if project else f"Project {contract_id_int}" + logger_handler.logger.info( + f"User {session['username']} updated employee: " + f"{employee_index} - {first_name} {last_name} assigned to {project_name}" + ) flash(f'Employee "{first_name} {last_name}" updated successfully.', 'success') return redirect(url_for('employees.employees')) @@ -241,16 +237,15 @@ def edit_employee(employee_index): @login_required @log_database_operations('employee_deletion') def delete_employee(employee_index): - """Delete employee (Admin only) - Enhanced with better logging""" + """Delete employee (Admin only)""" try: - print(f"🗑️ DELETE REQUEST: Employee index {employee_index}") - print(f"📋 Request method: {request.method}") - print(f"👤 User: {session.get('username', 'Unknown')}") - + logger_handler.logger.info( + f"User {session.get('username', 'Unknown')} initiated delete for employee index {employee_index}" + ) + # Get employee by index (primary key) employee = Employee.query.get_or_404(employee_index) - print(f"✅ Found employee: {employee.firstName} {employee.lastName} (ID: {employee.id})") - + # Store employee data for logging before deletion employee_data = { 'index': employee.index, @@ -260,44 +255,42 @@ def delete_employee(employee_index): 'title': employee.title, 'contractId': employee.contractId } - + # Check if employee has attendance records attendance_count = AttendanceData.query.filter_by(employee_id=str(employee.id)).count() - print(f"📊 Attendance records found: {attendance_count}") - + if attendance_count > 0: - error_msg = f'Cannot delete employee "{employee.full_name}". Employee has {attendance_count} attendance records. Please contact system administrator.' - print(f"❌ DELETION BLOCKED: {error_msg}") + error_msg = ( + f'Cannot delete employee "{employee.full_name}". ' + f'Employee has {attendance_count} attendance records. ' + f'Please contact system administrator.' + ) + logger_handler.logger.warning( + f"Deletion blocked for employee {employee_data['id']} " + f"({employee_data['firstName']} {employee_data['lastName']}): " + f"{attendance_count} attendance records exist" + ) flash(error_msg, 'error') return redirect(url_for('employees.employees')) - - # Proceed with deletion - print(f"🗑️ Proceeding with deletion of employee: {employee_data['firstName']} {employee_data['lastName']}") - + db.session.delete(employee) db.session.commit() - print("✅ Employee successfully deleted from database") - - # Log employee deletion - try: - logger_handler.logger.info(f"Admin user {session['username']} deleted employee: {employee_data['firstName']} {employee_data['lastName']} (ID: {employee_data['id']})") - print(f"📋 Deletion logged successfully") - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") - - success_msg = f'Employee "{employee_data["firstName"]} {employee_data["lastName"]}" deleted successfully.' - flash(success_msg, 'success') - print(f"✅ SUCCESS: {success_msg}") - + + logger_handler.logger.info( + f"User {session['username']} deleted employee: " + f"{employee_data['firstName']} {employee_data['lastName']} (ID: {employee_data['id']})" + ) + + flash( + f'Employee "{employee_data["firstName"]} {employee_data["lastName"]}" deleted successfully.', + 'success' + ) return redirect(url_for('employees.employees')) - + except Exception as e: db.session.rollback() logger_handler.log_database_error('employee_deletion', e) - error_msg = f'Error deleting employee. Please try again.' - print(f"❌ ERROR in delete_employee: {e}") - print(f"❌ Exception type: {type(e)}") - flash(error_msg, 'error') + flash('Error deleting employee. Please try again.', 'error') return redirect(url_for('employees.employees')) @bp.route('/api/employees/search', endpoint='api_employees_search') @@ -368,10 +361,9 @@ def employee_detail(employee_index): } # Log employee detail view - try: - logger_handler.logger.info(f"User {session['username']} viewed employee detail: {employee.full_name} (ID: {employee.id})") - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.logger.info( + f"User {session['username']} viewed employee detail: {employee.full_name} (ID: {employee.id})" + ) return render_template('employee_detail.html', employee=employee, @@ -380,4 +372,4 @@ def employee_detail(employee_index): except Exception as e: logger_handler.log_database_error('employee_detail', e) flash('Error loading employee details. Please try again.', 'error') - return redirect(url_for('employees.employees')) + return redirect(url_for('employees.employees')) \ No newline at end of file diff --git a/routes/users.py b/routes/users.py index cb20261..197d0a4 100644 --- a/routes/users.py +++ b/routes/users.py @@ -201,21 +201,6 @@ def create_user(): flash('Error loading form. Please try again.', 'error') return redirect(url_for('users.users')) -def get_all_locations_from_qr_codes(): - """Helper function to get all unique locations from QR codes""" - try: - result = db.session.execute(text(""" - SELECT DISTINCT location - FROM qr_codes - WHERE location IS NOT NULL - AND active_status = 1 - ORDER BY location - """)) - return [row[0] for row in result.fetchall()] - except Exception as e: - logger_handler.logger.error(f"Error loading locations: {e}") - return [] - @bp.route('/users//delete', methods=['GET', 'POST'], endpoint='delete_user') @admin_required def delete_user(user_id): @@ -244,14 +229,16 @@ def delete_user(user_id): user_to_delete.active_status = False db.session.commit() + logger_handler.logger.info( + f"Admin {current_user.username} deactivated user: {user_to_delete.username} (ID: {user_to_delete.id})" + ) flash(f'User "{user_to_delete.full_name}" has been deactivated successfully.', 'success') - print(f"Admin {current_user.username} deactivated user: {user_to_delete.username}") return redirect(url_for('users.users')) except Exception as e: db.session.rollback() - print(f"Error deactivating user: {e}") + logger_handler.log_database_error('user_deactivation', e) flash('Error deactivating user. Please try again.', 'error') return redirect(url_for('users.users')) @@ -272,14 +259,16 @@ def reactivate_user(user_id): else: user_to_reactivate.active_status = True db.session.commit() + logger_handler.logger.info( + f"Admin {current_user.username} reactivated user: {user_to_reactivate.username} (ID: {user_to_reactivate.id})" + ) flash(f'User "{user_to_reactivate.full_name}" has been reactivated successfully.', 'success') - print(f"Admin {current_user.username} reactivated user: {user_to_reactivate.username}") return redirect(url_for('users.users')) except Exception as e: db.session.rollback() - print(f"Error reactivating user: {e}") + logger_handler.log_database_error('user_reactivation', e) flash('Error reactivating user. Please try again.', 'error') return redirect(url_for('users.users')) @@ -300,14 +289,16 @@ def promote_user(user_id): else: user_to_promote.role = 'admin' db.session.commit() + logger_handler.logger.info( + f"Admin {current_user.username} promoted user {user_to_promote.username} (ID: {user_to_promote.id}) to admin" + ) flash(f'"{user_to_promote.full_name}" has been promoted to admin.', 'success') - print(f"Admin {current_user.username} promoted user {user_to_promote.username} to admin") return redirect(url_for('users.users')) except Exception as e: db.session.rollback() - print(f"Error promoting user: {e}") + logger_handler.log_database_error('user_promotion', e) flash('Error promoting user. Please try again.', 'error') return redirect(url_for('users.users')) @@ -339,14 +330,16 @@ def demote_user(user_id): else: user_to_demote.role = 'staff' db.session.commit() + logger_handler.logger.info( + f"Admin {current_user.username} demoted user {user_to_demote.username} (ID: {user_to_demote.id}) to staff" + ) flash(f'"{user_to_demote.full_name}" has been demoted to staff.', 'success') - print(f"Admin {current_user.username} demoted user {user_to_demote.username} to staff") return redirect(url_for('users.users')) except Exception as e: db.session.rollback() - print(f"Error demoting user: {e}") + logger_handler.log_database_error('user_demotion', e) flash('Error demoting user. Please try again.', 'error') return redirect(url_for('users.users')) @@ -602,9 +595,9 @@ def toggle_user_status(user_id): message = f'"{user_to_toggle.full_name}" has been {action} successfully.' # Log status change - logger_handler.logger.info(f"Admin {current_user.username} {action} user {user_to_toggle.username}") - - print(f"Admin {current_user.username} {action} user {user_to_toggle.username}") + logger_handler.logger.info( + f"Admin {current_user.username} {action} user {user_to_toggle.username} (ID: {user_to_toggle.id})" + ) return jsonify({ 'success': True, @@ -616,7 +609,6 @@ def toggle_user_status(user_id): except Exception as e: db.session.rollback() logger_handler.log_database_error('user_status_toggle', e) - print(f"Error toggling user status: {e}") return jsonify({ 'success': False, 'message': 'Error updating user status. Please try again.' @@ -641,17 +633,17 @@ def activate_user(user_id): db.session.commit() # Log activation - logger_handler.logger.info(f"Admin {current_user.username} activated user {user_to_activate.username}") + logger_handler.logger.info( + f"Admin {current_user.username} activated user {user_to_activate.username} (ID: {user_to_activate.id})" + ) flash(f'"{user_to_activate.full_name}" has been activated.', 'success') - print(f"Admin {current_user.username} activated user {user_to_activate.username}") return redirect(url_for('users.users')) except Exception as e: db.session.rollback() logger_handler.log_database_error('user_activation', e) - print(f"Error activating user: {e}") flash('Error activating user. Please try again.', 'error') return redirect(url_for('users.users')) @@ -686,17 +678,17 @@ def deactivate_user(user_id): db.session.commit() # Log deactivation - logger_handler.logger.info(f"Admin {current_user.username} deactivated user {user_to_deactivate.username}") + logger_handler.logger.info( + f"Admin {current_user.username} deactivated user {user_to_deactivate.username} (ID: {user_to_deactivate.id})" + ) flash(f'"{user_to_deactivate.full_name}" has been deactivated.', 'success') - print(f"Admin {current_user.username} deactivated user {user_to_deactivate.username}") return redirect(url_for('users.users')) except Exception as e: db.session.rollback() logger_handler.log_database_error('user_deactivation', e) - print(f"Error deactivating user: {e}") flash('Error deactivating user. Please try again.', 'error') return redirect(url_for('users.users')) @@ -741,7 +733,6 @@ def user_stats_api(): except Exception as e: logger_handler.log_database_error('user_stats_api', e) - print(f"Error fetching user stats: {e}") return jsonify({'error': 'Failed to fetch user statistics'}), 500 @bp.route('/api/locations-by-projects', methods=['POST'], endpoint='get_locations_by_projects') @@ -802,7 +793,7 @@ def role_permissions_api(): }) except Exception as e: - print(f"Error fetching role permissions: {e}") + logger_handler.log_database_error('role_permissions_api', e) return jsonify({'error': 'Failed to fetch role permissions'}), 500 @bp.route('/api/geocode', methods=['POST'], endpoint='geocode_address_api') @@ -820,20 +811,16 @@ def geocode_address_api(): }), 400 # Log API geocoding request - try: - logger_handler.log_user_activity('api_geocoding_request', f'API geocoding request: {address[:50]}...') - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.logger.info(f"API geocoding request from user {session.get('username', 'unknown')}: {address[:50]}") # Use the enhanced function that returns 3 values lat, lng, accuracy = get_coordinates_from_address_enhanced(address) if lat is not None and lng is not None: - # Log successful API geocoding - try: - logger_handler.log_user_activity('api_geocoding_success', f'API geocoding success: {address[:50]}... -> {lat}, {lng} ({accuracy})') - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.logger.info( + f"API geocoding success for user {session.get('username', 'unknown')}: " + f"{address[:50]} -> {lat}, {lng} ({accuracy})" + ) return jsonify({ 'success': True, @@ -847,11 +834,9 @@ def geocode_address_api(): 'message': f'Address geocoded successfully with {accuracy} accuracy using {"Google Maps" if gmaps_client else "OpenStreetMap"}' }) else: - # Log failed API geocoding - try: - logger_handler.log_user_activity('api_geocoding_failed', f'API geocoding failed: {address[:50]}...') - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.logger.warning( + f"API geocoding failed for user {session.get('username', 'unknown')}: {address[:50]}" + ) return jsonify({ 'success': False, @@ -859,13 +844,7 @@ def geocode_address_api(): }), 404 except Exception as e: - print(f"❌ Geocoding API error: {e}") - - # Log API geocoding error - try: - logger_handler.log_flask_error('api_geocoding_error', f'API geocoding error: {str(e)}') - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.log_flask_error('api_geocoding_error', f'API geocoding error: {str(e)}') return jsonify({ 'success': False, @@ -888,10 +867,9 @@ def reverse_geocode_api(): }), 400 # Log API reverse geocoding request - try: - logger_handler.log_user_activity('api_reverse_geocoding_request', f'API reverse geocoding: {latitude}, {longitude}') - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.logger.info( + f"API reverse geocoding request from user {session.get('username', 'unknown')}: {latitude}, {longitude}" + ) # Use the reverse geocoding function address = reverse_geocode_coordinates(latitude, longitude) @@ -913,13 +891,7 @@ def reverse_geocode_api(): }), 404 except Exception as e: - print(f"❌ Reverse geocoding API error: {e}") - - # Log API reverse geocoding error - try: - logger_handler.log_flask_error('api_reverse_geocoding_error', f'API reverse geocoding error: {str(e)}') - except Exception as log_error: - print(f"⚠️ Logging error (non-critical): {log_error}") + logger_handler.log_flask_error('api_reverse_geocoding_error', f'API reverse geocoding error: {str(e)}') return jsonify({ 'success': False, @@ -977,16 +949,21 @@ def permanently_delete_user(user_id): db.session.delete(user_to_delete) db.session.commit() - # Updated flash message to reflect QR codes are preserved - flash(f'User "{user_name}" has been permanently deleted. {user_qr_count} QR codes created by this user are now orphaned but preserved.', 'success') - print(f"Admin {current_user.username} permanently deleted user: {username}, preserved {user_qr_count} QR codes") + logger_handler.logger.info( + f"Admin {current_user.username} permanently deleted user: {username}, " + f"preserved {user_qr_count} QR codes" + ) + flash( + f'User "{user_name}" has been permanently deleted. ' + f'{user_qr_count} QR codes created by this user are now orphaned but preserved.', + 'success' + ) return redirect(url_for('users.users')) except Exception as e: db.session.rollback() logger_handler.log_database_error('user_permanent_deletion', e) - print(f"Error permanently deleting user: {e}") flash('Error deleting user. Please try again.', 'error') return redirect(url_for('users.users')) diff --git a/working_hours_calculator.py b/working_hours_calculator.py index b130c50..1212a6a 100644 --- a/working_hours_calculator.py +++ b/working_hours_calculator.py @@ -23,8 +23,11 @@ from typing import List, Dict, Optional, Tuple, Any from dataclasses import dataclass import math import re +import logging from logger_handler import log_database_operations +_calc_logger = logging.getLogger('qr_attendance_app') + # Constants from Java implementation RECORD_GROUPING_MAX_MINUTES = 60 * 6 # 6 hours MAX_REGULAR_TIME_MINUTES = 60 * 40 # 40 hours per week @@ -402,7 +405,7 @@ class WorkingHoursCalculator: Dictionary containing daily and weekly hour calculations with SP/PW/PT breakdown """ try: - print(f"🔍 Calculating hours for base employee {employee_id} with SP/PW/PT support") + _calc_logger.debug(f"Calculating hours for base employee {employee_id} with SP/PW/PT support") # Parse base employee ID base_employee_id, _ = parse_employee_id_for_work_type(employee_id) @@ -460,11 +463,15 @@ class WorkingHoursCalculator: records_by_type[work_type].append(att_record) except Exception as record_error: - print(f"⚠️ Error processing record: {record_error}") + _calc_logger.warning(f"Error processing attendance record: {record_error}") continue total_records = sum(len(records_by_type[wt]) for wt in records_by_type) - print(f"📊 Found {total_records} records - Regular: {len(records_by_type['regular'])}, SP: {len(records_by_type['SP'])}, PW: {len(records_by_type['PW'])}, PT: {len(records_by_type['PT'])}") + _calc_logger.debug( + f"Employee {employee_id}: {total_records} records found — " + f"Regular: {len(records_by_type['regular'])}, SP: {len(records_by_type['SP'])}, " + f"PW: {len(records_by_type['PW'])}, PT: {len(records_by_type['PT'])}" + ) # Group records by date for each work type daily_records_by_type = {wt: {} for wt in ['regular', 'SP', 'PW', 'PT']} @@ -559,8 +566,10 @@ class WorkingHoursCalculator: # Move orphaned early check-outs from Day N+1 → Day N for co in orphaned_early_outs[:len(unmatched_late_ins)]: - print(f"🌙 Overnight shift detected for {work_type} on {date_key}: " - f"moving check-out {co.check_in_time} from {next_date_key} → {date_key}") + _calc_logger.info( + f"Overnight shift detected for work_type={work_type} on {date_key}: " + f"moving check-out {co.check_in_time} from {next_date_key} -> {date_key}" + ) daily_records_by_type[work_type][date_key].append(co) daily_records_by_type[work_type][next_date_key].remove(co) @@ -697,7 +706,11 @@ class WorkingHoursCalculator: grand_pw_hours = round_base100_hours(sum(week['pw_hours'] for week in weekly_hours)) grand_pt_hours = round_base100_hours(sum(week.get('pt_hours', 0) for week in weekly_hours)) - print(f"✅ Employee {employee_id}: Total: {grand_total_hours:.2f}h (Regular: {grand_regular_hours:.2f}h, OT: {grand_overtime_hours:.2f}h, SP: {grand_sp_hours:.2f}h, PW: {grand_pw_hours:.2f}h, PT: {grand_pt_hours:.2f}h)") + _calc_logger.info( + f"Employee {employee_id}: Total={grand_total_hours:.2f}h " + f"(Regular={grand_regular_hours:.2f}h, OT={grand_overtime_hours:.2f}h, " + f"SP={grand_sp_hours:.2f}h, PW={grand_pw_hours:.2f}h, PT={grand_pt_hours:.2f}h)" + ) return { 'employee_id': employee_id, @@ -723,9 +736,7 @@ class WorkingHoursCalculator: } except Exception as e: - print(f"❌ Error calculating working hours for employee {employee_id}: {e}") - import traceback - print(f"❌ Traceback: {traceback.format_exc()}") + _calc_logger.error(f"Error calculating working hours for employee {employee_id}: {e}", exc_info=True) raise e def calculate_all_employees_hours(self, start_date: datetime, end_date: datetime, @@ -745,7 +756,7 @@ class WorkingHoursCalculator: Dictionary containing hours data for all employees with SP/PW/PT breakdown """ try: - print(f"🚀 Starting calculation for all employees with SP/PW/PT consolidation") + _calc_logger.info("Starting hours calculation for all employees with SP/PW/PT consolidation") # Get unique BASE employee IDs (consolidate SP/PW/PT variants) base_employee_ids = set() @@ -762,20 +773,20 @@ class WorkingHoursCalculator: base_employee_ids.add(base_id) except Exception as e: - print(f"⚠️ Error processing employee ID: {e}") + _calc_logger.warning(f"Error processing employee ID during consolidation: {e}") continue - print(f"👥 Found {len(base_employee_ids)} unique base employees (after consolidation)") + _calc_logger.info(f"Found {len(base_employee_ids)} unique base employees (after SP/PW/PT consolidation)") results = {} for base_emp_id in sorted(base_employee_ids): try: - print(f"\n🔄 Processing base employee {base_emp_id}") + _calc_logger.debug(f"Processing base employee {base_emp_id}") results[base_emp_id] = self.calculate_employee_hours( base_emp_id, start_date, end_date, attendance_records ) except Exception as e: - print(f"❌ Error processing employee {base_emp_id}: {e}") + _calc_logger.error(f"Error processing employee {base_emp_id}: {e}", exc_info=True) # Return empty result for this employee results[base_emp_id] = { 'employee_id': base_emp_id, @@ -810,7 +821,5 @@ class WorkingHoursCalculator: } except Exception as e: - print(f"❌ Error calculating hours for all employees: {e}") - import traceback - print(f"❌ Traceback: {traceback.format_exc()}") + _calc_logger.error(f"Error calculating hours for all employees: {e}", exc_info=True) raise e \ No newline at end of file