Added new role Accounting

This commit is contained in:
2025-12-23 17:16:36 -05:00
parent c991069d30
commit 46a7b1a3b7
7 changed files with 69 additions and 18 deletions
+34 -14
View File
@@ -218,10 +218,10 @@ def cache_coordinates(address, lat, lng, accuracy):
print(f"⚠️ Error caching coordinates: {e}") print(f"⚠️ Error caching coordinates: {e}")
# Valid user roles with new additions # 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) # 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 # Import and initialize models
from models import set_db from models import set_db
@@ -303,6 +303,24 @@ def get_role_permissions(role):
'Cannot manage other users', 'Cannot manage other users',
'Cannot access admin settings' '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, {}) return permissions.get(role, {})
@@ -2659,6 +2677,7 @@ def user_stats_api():
staff_users = User.query.filter_by(role='staff', active_status=True).count() staff_users = User.query.filter_by(role='staff', active_status=True).count()
payroll_users = User.query.filter_by(role='payroll', 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() 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() inactive_users = User.query.filter_by(active_status=False).count()
recent_registrations = User.query.filter( recent_registrations = User.query.filter(
@@ -2676,6 +2695,7 @@ def user_stats_api():
'staff_users': staff_users, 'staff_users': staff_users,
'payroll_users': payroll_users, 'payroll_users': payroll_users,
'project_manager_users': project_manager_users, 'project_manager_users': project_manager_users,
'accounting_users': accounting_users,
'inactive_users': inactive_users, 'inactive_users': inactive_users,
'recent_registrations': recent_registrations, 'recent_registrations': recent_registrations,
'recent_logins': recent_logins 'recent_logins': recent_logins
@@ -5132,7 +5152,7 @@ def attendance_report():
def edit_attendance(record_id): def edit_attendance(record_id):
"""Edit attendance record (Admin and Payroll only)""" """Edit attendance record (Admin and Payroll only)"""
# Check if user has permission to edit attendance records # 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') flash('Access denied. Only administrators and payroll staff can edit attendance records.', 'error')
return redirect(url_for('attendance_report')) return redirect(url_for('attendance_report'))
@@ -5252,7 +5272,7 @@ def edit_attendance(record_id):
def delete_attendance(record_id): def delete_attendance(record_id):
"""Delete attendance record (Admin and Payroll only)""" """Delete attendance record (Admin and Payroll only)"""
# Check if user has permission to delete attendance records # 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': if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({ return jsonify({
'success': False, 'success': False,
@@ -5320,7 +5340,7 @@ def verification_review():
"""Admin page to review pending photo verifications""" """Admin page to review pending photo verifications"""
try: try:
# Only admins can access # 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') flash('Unauthorized access.', 'error')
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
@@ -5382,7 +5402,7 @@ def update_verification_status(record_id):
"""Update verification status (approve/reject)""" """Update verification status (approve/reject)"""
try: try:
# Only admins can update # Only admins can update
if session.get('role') not in ['admin', 'payroll']: if session.get('role') not in ['admin', 'payroll', 'accounting']:
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': 'Unauthorized access' 'message': 'Unauthorized access'
@@ -5448,7 +5468,7 @@ def get_verification_details(record_id):
# Check if user has permission to view # Check if user has permission to view
# Allow admin and payroll staff to view verification details # 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({ return jsonify({
'success': False, 'success': False,
'message': 'Unauthorized access' 'message': 'Unauthorized access'
@@ -5567,7 +5587,7 @@ def export_configuration():
"""Display export configuration page for customizing Excel exports""" """Display export configuration page for customizing Excel exports"""
try: try:
user_role = session.get('role') 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") 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') flash('Access denied. Only administrators and payroll staff can access export configuration.', 'error')
return redirect(url_for('attendance_report')) 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""" """Generate and download Excel file with selected columns in specified order"""
try: try:
user_role = session.get('role') 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") 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') flash('Access denied. Only administrators and payroll staff can export data.', 'error')
return redirect(url_for('attendance_report')) return redirect(url_for('attendance_report'))
@@ -6293,7 +6313,7 @@ def payroll_dashboard():
try: try:
# Check if user has payroll access # Check if user has payroll access
user_role = session.get('role') 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") 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') flash('Access denied. Only administrators and payroll staff can access payroll features.', 'error')
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
@@ -6440,7 +6460,7 @@ def export_payroll_excel():
try: try:
# Check permissions # Check permissions
user_role = session.get('role') 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") 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') flash('Access denied. Only administrators and payroll staff can export payroll data.', 'error')
return redirect(url_for('payroll_dashboard')) return redirect(url_for('payroll_dashboard'))
@@ -6681,7 +6701,7 @@ def calculate_working_hours_api():
try: try:
# Check permissions # Check permissions
user_role = session.get('role') user_role = session.get('role')
if user_role not in ['admin', 'payroll']: if user_role not in ['admin', 'payroll', 'accounting']:
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': 'Access denied. Insufficient permissions.' 'message': 'Access denied. Insufficient permissions.'
@@ -6758,7 +6778,7 @@ def get_miss_punch_details(employee_id):
try: try:
# Check permissions # Check permissions
user_role = session.get('role') user_role = session.get('role')
if user_role not in ['admin', 'payroll']: if user_role not in ['admin', 'payroll', 'accounting']:
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': 'Access denied. Insufficient permissions.' 'message': 'Access denied. Insufficient permissions.'
@@ -7152,7 +7172,7 @@ def export_statistics():
"""Export statistics data to CSV/Excel""" """Export statistics data to CSV/Excel"""
try: try:
# Check permissions # 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 return jsonify({'error': 'Access denied'}), 403
# Log export attempt # Log export attempt
+3 -2
View File
@@ -10,7 +10,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime from datetime import datetime
# Valid user roles (kept in sync with app.py) # 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 db from app - this works because app.py imports this file after db is created
import sys import sys
@@ -63,6 +63,7 @@ class User(base.db.Model):
'admin': 'Administrator', 'admin': 'Administrator',
'staff': 'Staff User', 'staff': 'Staff User',
'payroll': 'Payroll Specialist', 'payroll': 'Payroll Specialist',
'project_manager': 'Project Manager' 'project_manager': 'Project Manager',
'accounting': 'Accounting Specialist'
} }
return role_names.get(self.role, self.role.title()) return role_names.get(self.role, self.role.title())
+14
View File
@@ -107,6 +107,10 @@
background: linear-gradient(90deg, #17a2b8, #138496); background: linear-gradient(90deg, #17a2b8, #138496);
} }
.stat-card.accounting::before {
background: linear-gradient(90deg, #059669, #047857);
}
.stat-card.project-manager::before { .stat-card.project-manager::before {
background: linear-gradient(90deg, #6f42c1, #5a2d91); background: linear-gradient(90deg, #6f42c1, #5a2d91);
} }
@@ -149,6 +153,10 @@
background: linear-gradient(135deg, #17a2b8, #138496); background: linear-gradient(135deg, #17a2b8, #138496);
} }
.stat-icon.accounting {
background: linear-gradient(135deg, #059669, #047857);
}
.stat-icon.project-manager { .stat-icon.project-manager {
background: linear-gradient(135deg, #6f42c1, #5a2d91); background: linear-gradient(135deg, #6f42c1, #5a2d91);
} }
@@ -385,6 +393,12 @@
border: 1px solid rgba(23, 162, 184, 0.3); 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 { .user-role.project_manager {
background-color: rgba(111, 66, 193, 0.1); background-color: rgba(111, 66, 193, 0.1);
color: #5a2d91; color: #5a2d91;
+1 -1
View File
@@ -101,7 +101,7 @@
<i class="fas fa-clipboard-list"></i> <i class="fas fa-clipboard-list"></i>
<span class="menu-text">System Logs</span> <span class="menu-text">System Logs</span>
</a> </a>
{% elif session.role in ['payroll'] %} {% elif session.role in ['payroll', 'accounting'] %}
<a href="{{ url_for('employees') }}" class="menu-item"> <a href="{{ url_for('employees') }}" class="menu-item">
<i class="fas fa-user"></i> <i class="fas fa-user"></i>
<span class="menu-text">Employees</span> <span class="menu-text">Employees</span>
+1
View File
@@ -82,6 +82,7 @@ Code Management{% endblock %} {% block content %}
<option value="">Select Role</option> <option value="">Select Role</option>
<option value="staff">Staff User</option> <option value="staff">Staff User</option>
<option value="payroll">Payroll Specialist</option> <option value="payroll">Payroll Specialist</option>
<option value="accounting">Accounting Specialist</option>
<option value="project_manager">Project Manager</option> <option value="project_manager">Project Manager</option>
<option value="admin">Administrator</option> <option value="admin">Administrator</option>
</select> </select>
+3 -1
View File
@@ -64,6 +64,7 @@
<select id="role" name="role" required> <select id="role" name="role" required>
<option value="staff" {{ 'selected' if user.role == 'staff' else '' }}>Staff User</option> <option value="staff" {{ 'selected' if user.role == 'staff' else '' }}>Staff User</option>
<option value="payroll" {{ 'selected' if user.role == 'payroll' else '' }}>Payroll Specialist</option> <option value="payroll" {{ 'selected' if user.role == 'payroll' else '' }}>Payroll Specialist</option>
<option value="accounting" {{ 'selected' if user.role == 'accounting' else '' }}>Accounting Specialist</option>
<option value="project_manager" {{ 'selected' if user.role == 'project_manager' else '' }}>Project Manager</option> <option value="project_manager" {{ 'selected' if user.role == 'project_manager' else '' }}>Project Manager</option>
<option value="admin" {{ 'selected' if user.role == 'admin' else '' }}>Administrator</option> <option value="admin" {{ 'selected' if user.role == 'admin' else '' }}>Administrator</option>
</select> </select>
@@ -586,7 +587,8 @@
function getRoleDisplayName(role) { function getRoleDisplayName(role) {
const roleNames = { const roleNames = {
'staff': 'Staff User', 'staff': 'Staff User',
'payroll': 'Payroll Specialist', 'payroll': 'Payroll Specialist',
'accounting': 'Accounting Specialist',
'project_manager': 'Project Manager', 'project_manager': 'Project Manager',
'admin': 'Administrator' 'admin': 'Administrator'
}; };
+13
View File
@@ -65,6 +65,19 @@ Code Management{% endblock %} {% block extra_head %}
</div> </div>
</div> </div>
<div class="stat-card accounting">
<div class="stat-icon accounting">
<i class="fas fa-file-invoice-dollar"></i>
</div>
<div class="stat-info">
<h3>
{{ users|selectattr('role', 'equalto',
'accounting')|selectattr('active_status', 'equalto', True)|list|length }}
</h3>
<p>Accounting Specialists</p>
</div>
</div>
<div class="stat-card project-manager"> <div class="stat-card project-manager">
<div class="stat-icon project-manager"> <div class="stat-icon project-manager">
<i class="fas fa-project-diagram"></i> <i class="fas fa-project-diagram"></i>