Mar. 23 2026 patch code
This commit is contained in:
+4
-4
@@ -141,7 +141,7 @@ def login():
|
|||||||
)
|
)
|
||||||
|
|
||||||
flash(f'Welcome back, {user.full_name}!', 'success')
|
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
|
# Redirect to intended page or dashboard
|
||||||
next_page = request.args.get('next')
|
next_page = request.args.get('next')
|
||||||
@@ -158,11 +158,11 @@ def login():
|
|||||||
)
|
)
|
||||||
|
|
||||||
flash('Invalid username or password.', 'error')
|
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:
|
except Exception as e:
|
||||||
logger_handler.log_database_error('user_login', 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')
|
flash('Login error. Please try again.', 'error')
|
||||||
|
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
@@ -261,4 +261,4 @@ def profile():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_handler.log_database_error('profile_update', e)
|
logger_handler.log_database_error('profile_update', e)
|
||||||
flash('Profile update failed. Please try again.', 'error')
|
flash('Profile update failed. Please try again.', 'error')
|
||||||
return redirect(url_for('dashboard.dashboard'))
|
return redirect(url_for('dashboard.dashboard'))
|
||||||
+49
-57
@@ -32,11 +32,7 @@ bp = Blueprint('employees', __name__)
|
|||||||
def employees():
|
def employees():
|
||||||
"""Display employee management page with search and pagination"""
|
"""Display employee management page with search and pagination"""
|
||||||
try:
|
try:
|
||||||
# Log user accessing employee management
|
logger_handler.logger.info(f"User {session['username']} accessed employee management list")
|
||||||
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}")
|
|
||||||
|
|
||||||
# Get search parameters
|
# Get search parameters
|
||||||
search = request.args.get('search', '').strip()
|
search = request.args.get('search', '').strip()
|
||||||
@@ -138,12 +134,12 @@ def create_employee():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Log employee creation with project info
|
# Log employee creation with project info
|
||||||
try:
|
project = Project.query.get(contract_id_int)
|
||||||
project = Project.query.get(contract_id_int)
|
project_name = project.name if project else f"Project {contract_id_int}"
|
||||||
project_name = project.name if project else f"Project {contract_id_int}"
|
logger_handler.logger.info(
|
||||||
logger_handler.logger.info(f"Admin user {session['username']} created new employee: {employee_id_int} - {first_name} {last_name} assigned to {project_name}")
|
f"User {session['username']} created new employee: "
|
||||||
except Exception as log_error:
|
f"{employee_id_int} - {first_name} {last_name} assigned to {project_name}"
|
||||||
print(f"⚠️ Logging error (non-critical): {log_error}")
|
)
|
||||||
|
|
||||||
flash(f'Employee "{first_name} {last_name}" (ID: {employee_id}) created successfully.', 'success')
|
flash(f'Employee "{first_name} {last_name}" (ID: {employee_id}) created successfully.', 'success')
|
||||||
return redirect(url_for('employees.employees'))
|
return redirect(url_for('employees.employees'))
|
||||||
@@ -215,14 +211,14 @@ def edit_employee(employee_index):
|
|||||||
employee.contractId = contract_id_int
|
employee.contractId = contract_id_int
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Log employee update with project info
|
# Log employee update with project info
|
||||||
try:
|
project = Project.query.get(contract_id_int)
|
||||||
project = Project.query.get(contract_id_int)
|
project_name = project.name if project else f"Project {contract_id_int}"
|
||||||
project_name = project.name if project else f"Project {contract_id_int}"
|
logger_handler.logger.info(
|
||||||
logger_handler.logger.info(f"Admin user {session['username']} updated employee: {employee_index} - {first_name} {last_name} assigned to {project_name}")
|
f"User {session['username']} updated employee: "
|
||||||
except Exception as log_error:
|
f"{employee_index} - {first_name} {last_name} assigned to {project_name}"
|
||||||
print(f"⚠️ Logging error (non-critical): {log_error}")
|
)
|
||||||
|
|
||||||
flash(f'Employee "{first_name} {last_name}" updated successfully.', 'success')
|
flash(f'Employee "{first_name} {last_name}" updated successfully.', 'success')
|
||||||
return redirect(url_for('employees.employees'))
|
return redirect(url_for('employees.employees'))
|
||||||
@@ -241,16 +237,15 @@ def edit_employee(employee_index):
|
|||||||
@login_required
|
@login_required
|
||||||
@log_database_operations('employee_deletion')
|
@log_database_operations('employee_deletion')
|
||||||
def delete_employee(employee_index):
|
def delete_employee(employee_index):
|
||||||
"""Delete employee (Admin only) - Enhanced with better logging"""
|
"""Delete employee (Admin only)"""
|
||||||
try:
|
try:
|
||||||
print(f"🗑️ DELETE REQUEST: Employee index {employee_index}")
|
logger_handler.logger.info(
|
||||||
print(f"📋 Request method: {request.method}")
|
f"User {session.get('username', 'Unknown')} initiated delete for employee index {employee_index}"
|
||||||
print(f"👤 User: {session.get('username', 'Unknown')}")
|
)
|
||||||
|
|
||||||
# Get employee by index (primary key)
|
# Get employee by index (primary key)
|
||||||
employee = Employee.query.get_or_404(employee_index)
|
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
|
# Store employee data for logging before deletion
|
||||||
employee_data = {
|
employee_data = {
|
||||||
'index': employee.index,
|
'index': employee.index,
|
||||||
@@ -260,44 +255,42 @@ def delete_employee(employee_index):
|
|||||||
'title': employee.title,
|
'title': employee.title,
|
||||||
'contractId': employee.contractId
|
'contractId': employee.contractId
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if employee has attendance records
|
# Check if employee has attendance records
|
||||||
attendance_count = AttendanceData.query.filter_by(employee_id=str(employee.id)).count()
|
attendance_count = AttendanceData.query.filter_by(employee_id=str(employee.id)).count()
|
||||||
print(f"📊 Attendance records found: {attendance_count}")
|
|
||||||
|
|
||||||
if attendance_count > 0:
|
if attendance_count > 0:
|
||||||
error_msg = f'Cannot delete employee "{employee.full_name}". Employee has {attendance_count} attendance records. Please contact system administrator.'
|
error_msg = (
|
||||||
print(f"❌ DELETION BLOCKED: {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')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for('employees.employees'))
|
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.delete(employee)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
print("✅ Employee successfully deleted from database")
|
|
||||||
|
logger_handler.logger.info(
|
||||||
# Log employee deletion
|
f"User {session['username']} deleted employee: "
|
||||||
try:
|
f"{employee_data['firstName']} {employee_data['lastName']} (ID: {employee_data['id']})"
|
||||||
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:
|
flash(
|
||||||
print(f"⚠️ Logging error (non-critical): {log_error}")
|
f'Employee "{employee_data["firstName"]} {employee_data["lastName"]}" deleted successfully.',
|
||||||
|
'success'
|
||||||
success_msg = f'Employee "{employee_data["firstName"]} {employee_data["lastName"]}" deleted successfully.'
|
)
|
||||||
flash(success_msg, 'success')
|
|
||||||
print(f"✅ SUCCESS: {success_msg}")
|
|
||||||
|
|
||||||
return redirect(url_for('employees.employees'))
|
return redirect(url_for('employees.employees'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
logger_handler.log_database_error('employee_deletion', e)
|
logger_handler.log_database_error('employee_deletion', e)
|
||||||
error_msg = f'Error deleting employee. Please try again.'
|
flash('Error deleting employee. Please try again.', 'error')
|
||||||
print(f"❌ ERROR in delete_employee: {e}")
|
|
||||||
print(f"❌ Exception type: {type(e)}")
|
|
||||||
flash(error_msg, 'error')
|
|
||||||
return redirect(url_for('employees.employees'))
|
return redirect(url_for('employees.employees'))
|
||||||
|
|
||||||
@bp.route('/api/employees/search', endpoint='api_employees_search')
|
@bp.route('/api/employees/search', endpoint='api_employees_search')
|
||||||
@@ -368,10 +361,9 @@ def employee_detail(employee_index):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Log employee detail view
|
# Log employee detail view
|
||||||
try:
|
logger_handler.logger.info(
|
||||||
logger_handler.logger.info(f"User {session['username']} viewed employee detail: {employee.full_name} (ID: {employee.id})")
|
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}")
|
|
||||||
|
|
||||||
return render_template('employee_detail.html',
|
return render_template('employee_detail.html',
|
||||||
employee=employee,
|
employee=employee,
|
||||||
@@ -380,4 +372,4 @@ def employee_detail(employee_index):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_handler.log_database_error('employee_detail', e)
|
logger_handler.log_database_error('employee_detail', e)
|
||||||
flash('Error loading employee details. Please try again.', 'error')
|
flash('Error loading employee details. Please try again.', 'error')
|
||||||
return redirect(url_for('employees.employees'))
|
return redirect(url_for('employees.employees'))
|
||||||
+48
-71
@@ -201,21 +201,6 @@ def create_user():
|
|||||||
flash('Error loading form. Please try again.', 'error')
|
flash('Error loading form. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
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/<int:user_id>/delete', methods=['GET', 'POST'], endpoint='delete_user')
|
@bp.route('/users/<int:user_id>/delete', methods=['GET', 'POST'], endpoint='delete_user')
|
||||||
@admin_required
|
@admin_required
|
||||||
def delete_user(user_id):
|
def delete_user(user_id):
|
||||||
@@ -244,14 +229,16 @@ def delete_user(user_id):
|
|||||||
user_to_delete.active_status = False
|
user_to_delete.active_status = False
|
||||||
db.session.commit()
|
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')
|
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'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
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')
|
flash('Error deactivating user. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
@@ -272,14 +259,16 @@ def reactivate_user(user_id):
|
|||||||
else:
|
else:
|
||||||
user_to_reactivate.active_status = True
|
user_to_reactivate.active_status = True
|
||||||
db.session.commit()
|
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')
|
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'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
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')
|
flash('Error reactivating user. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
@@ -300,14 +289,16 @@ def promote_user(user_id):
|
|||||||
else:
|
else:
|
||||||
user_to_promote.role = 'admin'
|
user_to_promote.role = 'admin'
|
||||||
db.session.commit()
|
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')
|
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'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
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')
|
flash('Error promoting user. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
@@ -339,14 +330,16 @@ def demote_user(user_id):
|
|||||||
else:
|
else:
|
||||||
user_to_demote.role = 'staff'
|
user_to_demote.role = 'staff'
|
||||||
db.session.commit()
|
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')
|
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'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
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')
|
flash('Error demoting user. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
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.'
|
message = f'"{user_to_toggle.full_name}" has been {action} successfully.'
|
||||||
|
|
||||||
# Log status change
|
# Log status change
|
||||||
logger_handler.logger.info(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})"
|
||||||
print(f"Admin {current_user.username} {action} user {user_to_toggle.username}")
|
)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
@@ -616,7 +609,6 @@ def toggle_user_status(user_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
logger_handler.log_database_error('user_status_toggle', e)
|
logger_handler.log_database_error('user_status_toggle', e)
|
||||||
print(f"Error toggling user status: {e}")
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Error updating user status. Please try again.'
|
'message': 'Error updating user status. Please try again.'
|
||||||
@@ -641,17 +633,17 @@ def activate_user(user_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Log activation
|
# 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')
|
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'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
logger_handler.log_database_error('user_activation', e)
|
logger_handler.log_database_error('user_activation', e)
|
||||||
print(f"Error activating user: {e}")
|
|
||||||
flash('Error activating user. Please try again.', 'error')
|
flash('Error activating user. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
@@ -686,17 +678,17 @@ def deactivate_user(user_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Log deactivation
|
# 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')
|
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'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
logger_handler.log_database_error('user_deactivation', e)
|
logger_handler.log_database_error('user_deactivation', e)
|
||||||
print(f"Error deactivating user: {e}")
|
|
||||||
flash('Error deactivating user. Please try again.', 'error')
|
flash('Error deactivating user. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
@@ -741,7 +733,6 @@ def user_stats_api():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_handler.log_database_error('user_stats_api', 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
|
return jsonify({'error': 'Failed to fetch user statistics'}), 500
|
||||||
|
|
||||||
@bp.route('/api/locations-by-projects', methods=['POST'], endpoint='get_locations_by_projects')
|
@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:
|
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
|
return jsonify({'error': 'Failed to fetch role permissions'}), 500
|
||||||
|
|
||||||
@bp.route('/api/geocode', methods=['POST'], endpoint='geocode_address_api')
|
@bp.route('/api/geocode', methods=['POST'], endpoint='geocode_address_api')
|
||||||
@@ -820,20 +811,16 @@ def geocode_address_api():
|
|||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# Log API geocoding request
|
# Log API geocoding request
|
||||||
try:
|
logger_handler.logger.info(f"API geocoding request from user {session.get('username', 'unknown')}: {address[:50]}")
|
||||||
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}")
|
|
||||||
|
|
||||||
# Use the enhanced function that returns 3 values
|
# Use the enhanced function that returns 3 values
|
||||||
lat, lng, accuracy = get_coordinates_from_address_enhanced(address)
|
lat, lng, accuracy = get_coordinates_from_address_enhanced(address)
|
||||||
|
|
||||||
if lat is not None and lng is not None:
|
if lat is not None and lng is not None:
|
||||||
# Log successful API geocoding
|
logger_handler.logger.info(
|
||||||
try:
|
f"API geocoding success for user {session.get('username', 'unknown')}: "
|
||||||
logger_handler.log_user_activity('api_geocoding_success', f'API geocoding success: {address[:50]}... -> {lat}, {lng} ({accuracy})')
|
f"{address[:50]} -> {lat}, {lng} ({accuracy})"
|
||||||
except Exception as log_error:
|
)
|
||||||
print(f"⚠️ Logging error (non-critical): {log_error}")
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'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"}'
|
'message': f'Address geocoded successfully with {accuracy} accuracy using {"Google Maps" if gmaps_client else "OpenStreetMap"}'
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# Log failed API geocoding
|
logger_handler.logger.warning(
|
||||||
try:
|
f"API geocoding failed for user {session.get('username', 'unknown')}: {address[:50]}"
|
||||||
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}")
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
@@ -859,13 +844,7 @@ def geocode_address_api():
|
|||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Geocoding API error: {e}")
|
logger_handler.log_flask_error('api_geocoding_error', f'API geocoding error: {str(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}")
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
@@ -888,10 +867,9 @@ def reverse_geocode_api():
|
|||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# Log API reverse geocoding request
|
# Log API reverse geocoding request
|
||||||
try:
|
logger_handler.logger.info(
|
||||||
logger_handler.log_user_activity('api_reverse_geocoding_request', f'API reverse geocoding: {latitude}, {longitude}')
|
f"API reverse geocoding request from user {session.get('username', 'unknown')}: {latitude}, {longitude}"
|
||||||
except Exception as log_error:
|
)
|
||||||
print(f"⚠️ Logging error (non-critical): {log_error}")
|
|
||||||
|
|
||||||
# Use the reverse geocoding function
|
# Use the reverse geocoding function
|
||||||
address = reverse_geocode_coordinates(latitude, longitude)
|
address = reverse_geocode_coordinates(latitude, longitude)
|
||||||
@@ -913,13 +891,7 @@ def reverse_geocode_api():
|
|||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Reverse geocoding API error: {e}")
|
logger_handler.log_flask_error('api_reverse_geocoding_error', f'API reverse geocoding error: {str(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}")
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
@@ -977,16 +949,21 @@ def permanently_delete_user(user_id):
|
|||||||
db.session.delete(user_to_delete)
|
db.session.delete(user_to_delete)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Updated flash message to reflect QR codes are preserved
|
logger_handler.logger.info(
|
||||||
flash(f'User "{user_name}" has been permanently deleted. {user_qr_count} QR codes created by this user are now orphaned but preserved.', 'success')
|
f"Admin {current_user.username} permanently deleted user: {username}, "
|
||||||
print(f"Admin {current_user.username} permanently deleted user: {username}, preserved {user_qr_count} QR codes")
|
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'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
logger_handler.log_database_error('user_permanent_deletion', e)
|
logger_handler.log_database_error('user_permanent_deletion', e)
|
||||||
print(f"Error permanently deleting user: {e}")
|
|
||||||
flash('Error deleting user. Please try again.', 'error')
|
flash('Error deleting user. Please try again.', 'error')
|
||||||
return redirect(url_for('users.users'))
|
return redirect(url_for('users.users'))
|
||||||
|
|
||||||
|
|||||||
+26
-17
@@ -23,8 +23,11 @@ from typing import List, Dict, Optional, Tuple, Any
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
from logger_handler import log_database_operations
|
from logger_handler import log_database_operations
|
||||||
|
|
||||||
|
_calc_logger = logging.getLogger('qr_attendance_app')
|
||||||
|
|
||||||
# Constants from Java implementation
|
# Constants from Java implementation
|
||||||
RECORD_GROUPING_MAX_MINUTES = 60 * 6 # 6 hours
|
RECORD_GROUPING_MAX_MINUTES = 60 * 6 # 6 hours
|
||||||
MAX_REGULAR_TIME_MINUTES = 60 * 40 # 40 hours per week
|
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
|
Dictionary containing daily and weekly hour calculations with SP/PW/PT breakdown
|
||||||
"""
|
"""
|
||||||
try:
|
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
|
# Parse base employee ID
|
||||||
base_employee_id, _ = parse_employee_id_for_work_type(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)
|
records_by_type[work_type].append(att_record)
|
||||||
|
|
||||||
except Exception as record_error:
|
except Exception as record_error:
|
||||||
print(f"⚠️ Error processing record: {record_error}")
|
_calc_logger.warning(f"Error processing attendance record: {record_error}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
total_records = sum(len(records_by_type[wt]) for wt in records_by_type)
|
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
|
# Group records by date for each work type
|
||||||
daily_records_by_type = {wt: {} for wt in ['regular', 'SP', 'PW', 'PT']}
|
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
|
# Move orphaned early check-outs from Day N+1 → Day N
|
||||||
for co in orphaned_early_outs[:len(unmatched_late_ins)]:
|
for co in orphaned_early_outs[:len(unmatched_late_ins)]:
|
||||||
print(f"🌙 Overnight shift detected for {work_type} on {date_key}: "
|
_calc_logger.info(
|
||||||
f"moving check-out {co.check_in_time} from {next_date_key} → {date_key}")
|
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][date_key].append(co)
|
||||||
daily_records_by_type[work_type][next_date_key].remove(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_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))
|
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 {
|
return {
|
||||||
'employee_id': employee_id,
|
'employee_id': employee_id,
|
||||||
@@ -723,9 +736,7 @@ class WorkingHoursCalculator:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error calculating working hours for employee {employee_id}: {e}")
|
_calc_logger.error(f"Error calculating working hours for employee {employee_id}: {e}", exc_info=True)
|
||||||
import traceback
|
|
||||||
print(f"❌ Traceback: {traceback.format_exc()}")
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def calculate_all_employees_hours(self, start_date: datetime, end_date: datetime,
|
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
|
Dictionary containing hours data for all employees with SP/PW/PT breakdown
|
||||||
"""
|
"""
|
||||||
try:
|
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)
|
# Get unique BASE employee IDs (consolidate SP/PW/PT variants)
|
||||||
base_employee_ids = set()
|
base_employee_ids = set()
|
||||||
@@ -762,20 +773,20 @@ class WorkingHoursCalculator:
|
|||||||
base_employee_ids.add(base_id)
|
base_employee_ids.add(base_id)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Error processing employee ID: {e}")
|
_calc_logger.warning(f"Error processing employee ID during consolidation: {e}")
|
||||||
continue
|
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 = {}
|
results = {}
|
||||||
for base_emp_id in sorted(base_employee_ids):
|
for base_emp_id in sorted(base_employee_ids):
|
||||||
try:
|
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(
|
results[base_emp_id] = self.calculate_employee_hours(
|
||||||
base_emp_id, start_date, end_date, attendance_records
|
base_emp_id, start_date, end_date, attendance_records
|
||||||
)
|
)
|
||||||
except Exception as e:
|
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
|
# Return empty result for this employee
|
||||||
results[base_emp_id] = {
|
results[base_emp_id] = {
|
||||||
'employee_id': base_emp_id,
|
'employee_id': base_emp_id,
|
||||||
@@ -810,7 +821,5 @@ class WorkingHoursCalculator:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error calculating hours for all employees: {e}")
|
_calc_logger.error(f"Error calculating hours for all employees: {e}", exc_info=True)
|
||||||
import traceback
|
|
||||||
print(f"❌ Traceback: {traceback.format_exc()}")
|
|
||||||
raise e
|
raise e
|
||||||
Reference in New Issue
Block a user