diff --git a/app.py b/app.py index d10c286..2054867 100644 --- a/app.py +++ b/app.py @@ -218,10 +218,10 @@ def cache_coordinates(address, lat, lng, accuracy): print(f"⚠️ Error caching coordinates: {e}") # Valid user roles with new additions -VALID_ROLES = ['admin', 'staff', 'payroll', 'project_manager'] +VALID_ROLES = ['admin', 'staff', 'payroll', 'project_manager', 'accounting'] # Roles that have staff-level permissions (non-admin roles) -STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager'] +STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager', 'accounting'] # Import and initialize models from models import set_db @@ -303,6 +303,24 @@ def get_role_permissions(role): 'Cannot manage other users', 'Cannot access admin settings' ] + }, + 'accounting': { + 'title': 'Accounting Specialist Permissions', + 'permissions': [ + 'View and modify employee records', + 'Access attendance reports and analytics', + 'View and manage time attendance data', + 'Export payroll and attendance data', + 'Access financial reports and statistics', + 'Update personal profile information', + 'Delete attendance records (same as payroll)' + ], + 'restrictions': [ + 'Cannot create or delete QR codes', + 'Cannot manage other users', + 'Cannot access admin settings', + 'Cannot manage projects' + ] } } return permissions.get(role, {}) @@ -2659,6 +2677,7 @@ def user_stats_api(): staff_users = User.query.filter_by(role='staff', active_status=True).count() payroll_users = User.query.filter_by(role='payroll', active_status=True).count() project_manager_users = User.query.filter_by(role='project_manager', active_status=True).count() + accounting_users = User.query.filter_by(role='accounting', active_status=True).count() inactive_users = User.query.filter_by(active_status=False).count() recent_registrations = User.query.filter( @@ -2676,6 +2695,7 @@ def user_stats_api(): 'staff_users': staff_users, 'payroll_users': payroll_users, 'project_manager_users': project_manager_users, + 'accounting_users': accounting_users, 'inactive_users': inactive_users, 'recent_registrations': recent_registrations, 'recent_logins': recent_logins @@ -5132,7 +5152,7 @@ def attendance_report(): def edit_attendance(record_id): """Edit attendance record (Admin and Payroll only)""" # Check if user has permission to edit attendance records - if session.get('role') not in ['admin', 'payroll']: + if session.get('role') not in ['admin', 'payroll', 'accounting']: flash('Access denied. Only administrators and payroll staff can edit attendance records.', 'error') return redirect(url_for('attendance_report')) @@ -5252,7 +5272,7 @@ def edit_attendance(record_id): def delete_attendance(record_id): """Delete attendance record (Admin and Payroll only)""" # Check if user has permission to delete attendance records - if session.get('role') not in ['admin', 'payroll']: + if session.get('role') not in ['admin', 'payroll', 'accounting']: if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return jsonify({ 'success': False, @@ -5320,7 +5340,7 @@ def verification_review(): """Admin page to review pending photo verifications""" try: # Only admins can access - if session.get('role') not in ['admin', 'payroll']: + if session.get('role') not in ['admin', 'payroll', 'accounting']: flash('Unauthorized access.', 'error') return redirect(url_for('dashboard')) @@ -5382,7 +5402,7 @@ def update_verification_status(record_id): """Update verification status (approve/reject)""" try: # Only admins can update - if session.get('role') not in ['admin', 'payroll']: + if session.get('role') not in ['admin', 'payroll', 'accounting']: return jsonify({ 'success': False, 'message': 'Unauthorized access' @@ -5448,7 +5468,7 @@ def get_verification_details(record_id): # Check if user has permission to view # Allow admin and payroll staff to view verification details - if session.get('role') not in ['admin', 'payroll']: + if session.get('role') not in ['admin', 'payroll', 'accounting']: return jsonify({ 'success': False, 'message': 'Unauthorized access' @@ -5567,7 +5587,7 @@ def export_configuration(): """Display export configuration page for customizing Excel exports""" try: user_role = session.get('role') - if user_role not in ['admin', 'payroll']: + if user_role not in ['admin', 'payroll', 'accounting']: logger_handler.logger.warning(f"User {session.get('username', 'unknown')} (role: {user_role}) attempted unauthorized access to export configuration") flash('Access denied. Only administrators and payroll staff can access export configuration.', 'error') return redirect(url_for('attendance_report')) @@ -5654,7 +5674,7 @@ def generate_excel_export(): """Generate and download Excel file with selected columns in specified order""" try: user_role = session.get('role') - if user_role not in ['admin', 'payroll']: + if user_role not in ['admin', 'payroll', 'accounting']: logger_handler.logger.warning(f"User {session.get('username', 'unknown')} (role: {user_role}) attempted unauthorized Excel export") flash('Access denied. Only administrators and payroll staff can export data.', 'error') return redirect(url_for('attendance_report')) @@ -6293,7 +6313,7 @@ def payroll_dashboard(): try: # Check if user has payroll access user_role = session.get('role') - if user_role not in ['admin', 'payroll']: + if user_role not in ['admin', 'payroll', 'accounting']: logger_handler.logger.warning(f"User {session.get('username', 'unknown')} (role: {user_role}) attempted to access payroll dashboard without permissions") flash('Access denied. Only administrators and payroll staff can access payroll features.', 'error') return redirect(url_for('dashboard')) @@ -6440,7 +6460,7 @@ def export_payroll_excel(): try: # Check permissions user_role = session.get('role') - if user_role not in ['admin', 'payroll']: + if user_role not in ['admin', 'payroll', 'accounting']: logger_handler.logger.warning(f"User {session.get('username', 'unknown')} (role: {user_role}) attempted unauthorized payroll Excel export") flash('Access denied. Only administrators and payroll staff can export payroll data.', 'error') return redirect(url_for('payroll_dashboard')) @@ -6681,7 +6701,7 @@ def calculate_working_hours_api(): try: # Check permissions user_role = session.get('role') - if user_role not in ['admin', 'payroll']: + if user_role not in ['admin', 'payroll', 'accounting']: return jsonify({ 'success': False, 'message': 'Access denied. Insufficient permissions.' @@ -6758,7 +6778,7 @@ def get_miss_punch_details(employee_id): try: # Check permissions user_role = session.get('role') - if user_role not in ['admin', 'payroll']: + if user_role not in ['admin', 'payroll', 'accounting']: return jsonify({ 'success': False, 'message': 'Access denied. Insufficient permissions.' @@ -7152,7 +7172,7 @@ def export_statistics(): """Export statistics data to CSV/Excel""" try: # Check permissions - if session.get('role') not in ['admin', 'payroll']: + if session.get('role') not in ['admin', 'payroll', 'accounting']: return jsonify({'error': 'Access denied'}), 403 # Log export attempt diff --git a/models/user.py b/models/user.py index dbe8298..a88bf47 100644 --- a/models/user.py +++ b/models/user.py @@ -10,7 +10,7 @@ from werkzeug.security import generate_password_hash, check_password_hash from datetime import datetime # Valid user roles (kept in sync with app.py) -STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager'] +STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager', 'accounting'] # Import db from app - this works because app.py imports this file after db is created import sys @@ -63,6 +63,7 @@ class User(base.db.Model): 'admin': 'Administrator', 'staff': 'Staff User', 'payroll': 'Payroll Specialist', - 'project_manager': 'Project Manager' + 'project_manager': 'Project Manager', + 'accounting': 'Accounting Specialist' } return role_names.get(self.role, self.role.title()) \ No newline at end of file diff --git a/static/css/users.css b/static/css/users.css index 45f041f..ed8bbc6 100644 --- a/static/css/users.css +++ b/static/css/users.css @@ -107,6 +107,10 @@ background: linear-gradient(90deg, #17a2b8, #138496); } +.stat-card.accounting::before { + background: linear-gradient(90deg, #059669, #047857); +} + .stat-card.project-manager::before { background: linear-gradient(90deg, #6f42c1, #5a2d91); } @@ -149,6 +153,10 @@ background: linear-gradient(135deg, #17a2b8, #138496); } +.stat-icon.accounting { + background: linear-gradient(135deg, #059669, #047857); +} + .stat-icon.project-manager { background: linear-gradient(135deg, #6f42c1, #5a2d91); } @@ -385,6 +393,12 @@ border: 1px solid rgba(23, 162, 184, 0.3); } +.user-role.accounting { + background-color: rgba(5, 150, 105, 0.1); + color: #047857; + border: 1px solid rgba(5, 150, 105, 0.3); +} + .user-role.project_manager { background-color: rgba(111, 66, 193, 0.1); color: #5a2d91; diff --git a/templates/base_authenticated.html b/templates/base_authenticated.html index c890b42..231cf06 100644 --- a/templates/base_authenticated.html +++ b/templates/base_authenticated.html @@ -101,7 +101,7 @@ System Logs - {% elif session.role in ['payroll'] %} + {% elif session.role in ['payroll', 'accounting'] %} Employees diff --git a/templates/create_user.html b/templates/create_user.html index 8d9177d..bffd38a 100644 --- a/templates/create_user.html +++ b/templates/create_user.html @@ -82,6 +82,7 @@ Code Management{% endblock %} {% block content %} + diff --git a/templates/edit_user.html b/templates/edit_user.html index 3620e3e..7148695 100644 --- a/templates/edit_user.html +++ b/templates/edit_user.html @@ -64,6 +64,7 @@ @@ -586,7 +587,8 @@ function getRoleDisplayName(role) { const roleNames = { 'staff': 'Staff User', - 'payroll': 'Payroll Specialist', + 'payroll': 'Payroll Specialist', + 'accounting': 'Accounting Specialist', 'project_manager': 'Project Manager', 'admin': 'Administrator' }; diff --git a/templates/users.html b/templates/users.html index 7e7b5e7..78b2746 100644 --- a/templates/users.html +++ b/templates/users.html @@ -65,6 +65,19 @@ Code Management{% endblock %} {% block extra_head %} +
+
+ +
+
+

+ {{ users|selectattr('role', 'equalto', + 'accounting')|selectattr('active_status', 'equalto', True)|list|length }} +

+

Accounting Specialists

+
+
+