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 @@
- {% elif session.role in ['payroll'] %}
+ {% elif session.role in ['payroll', 'accounting'] %}