Jul 15 - Add Auditor user role

This commit is contained in:
2026-07-15 13:47:59 -04:00
parent df547eefc2
commit fcb959900f
26 changed files with 152 additions and 56 deletions
+3 -2
View File
@@ -39,6 +39,7 @@ def index():
is_privileged = current_user.role in ['admin', 'director']
is_customer = current_user.role == 'customer'
is_project_manager = current_user.role == 'project_manager'
is_auditor = current_user.role == 'auditor'
# Resolve facility scope
customer_facility_ids = get_customer_scope(current_user) # None for non-customers
@@ -309,9 +310,9 @@ def index():
unassigned_open = len(unassigned_all)
unassigned_handler = _handler_split(unassigned_all)
# ── Inspector activity today (admin / director / PM only) ─────────────────
# ── Inspector activity today (admin / director / PM / auditor only) ───────
inspector_activity = []
if is_privileged or is_project_manager:
if is_privileged or is_project_manager or is_auditor:
active_inspectors = (
User.query
.filter_by(role='inspector', active=True)
+1 -1
View File
@@ -608,7 +608,7 @@ def execute(inspection_id):
return redirect(url_for('inspections.execute', inspection_id=inspection_id))
staff_for_flag_issue = User.query.filter(
User.role.in_(['admin', 'director', 'inspector', 'project_manager']),
User.role.in_(['director', 'inspector', 'project_manager', 'auditor']),
User.active == True,
).order_by(User.full_name, User.username).all()
+21 -14
View File
@@ -15,7 +15,7 @@ from app.models.notification import (
EVENT_CUSTOMER_ISSUE_UPDATED,
)
from app.utils.forms import IssueForm, IssueUpdateForm
from app.utils.decorators import supervisor_required
from app.utils.decorators import supervisor_required, issue_manager_required
from app.utils.notifications import notify, notify_customers_for_facility, notify_by_matrix
from app.utils.audit import log_action, ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE, ACTION_EXPORT
from app.utils.pdf_export import generate_issues_list_pdf
@@ -344,7 +344,7 @@ def index():
# Staff for quick-assign dropdown — same roles as the full issue form
staff = User.query.filter(
User.role.in_(['admin', 'director', 'inspector']), User.active == True
User.role.in_(['director', 'inspector', 'auditor']), User.active == True
).order_by(User.username).all()
# Reporters dropdown — users who have actually filed at least one issue
@@ -419,7 +419,14 @@ def view(issue_id):
return redirect(url_for('issues.view', issue_id=issue_id))
form = IssueUpdateForm(obj=issue)
staff = User.query.filter(User.role.in_(['admin', 'director', 'inspector'])).order_by(User.username).all()
staff = User.query.filter(User.role.in_(['director', 'inspector', 'auditor'])).order_by(User.username).all()
# Preserve any pre-existing assignee who is no longer in the assignable set
# (e.g. an admin assigned before admins were removed from the dropdown) so
# saving the form doesn't silently unassign them.
if issue.assigned_to and issue.assigned_to not in [u.id for u in staff]:
current_assignee = db.session.get(User, issue.assigned_to)
if current_assignee:
staff.append(current_assignee)
form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.display_name) for u in staff]
form.status.data = form.status.data or issue.status
@@ -429,7 +436,7 @@ def view(issue_id):
issue.status = form.status.data
if current_user.role in ['admin', 'director']:
if current_user.role in ['admin', 'director', 'auditor']:
issue.assigned_to = form.assigned_to.data or None
if form.status.data == 'resolved' and not issue.resolved_at:
@@ -447,8 +454,8 @@ def view(issue_id):
issue.result_notes = form.result_notes.data or None
# Handler assignment (who handles it) + vendor/facility details —
# admin, director, project_manager only.
if current_user.role in ('admin', 'director', 'project_manager'):
# admin, director, project_manager, auditor only.
if current_user.role in ('admin', 'director', 'project_manager', 'auditor'):
handler = form.handler_type.data or 'internal'
if handler not in ('internal', 'facility', 'vendor'):
handler = 'internal'
@@ -690,7 +697,7 @@ def unfollow(issue_id):
@bp.route('/new', methods=['GET', 'POST'])
@login_required
def create():
if current_user.role not in ('admin', 'director', 'customer'):
if current_user.role not in ('admin', 'director', 'customer', 'auditor'):
abort(403)
from app.models.project import Project, CustomerAssignment
@@ -712,7 +719,7 @@ def create():
else:
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
projects = Project.query.filter_by(active=True).order_by(Project.name).all()
staff = User.query.filter(User.role.in_(['admin', 'director', 'inspector'])).order_by(User.username).all()
staff = User.query.filter(User.role.in_(['director', 'inspector', 'auditor'])).order_by(User.username).all()
form.facility_id.choices = [(f.id, f.name) for f in facilities]
form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.display_name) for u in staff]
@@ -812,7 +819,7 @@ def create():
@bp.route('/<int:issue_id>/verify', methods=['POST'])
@login_required
@supervisor_required
@issue_manager_required
def verify(issue_id):
"""Supervisor sign-off: confirms resolution is satisfactory and closes the issue."""
issue = db.session.get(Issue, issue_id)
@@ -846,7 +853,7 @@ def verify(issue_id):
@bp.route('/bulk-verify', methods=['POST'])
@login_required
@supervisor_required
@issue_manager_required
def bulk_verify():
"""Verify multiple pending-verification issues in a single action."""
issue_ids = request.form.getlist('issue_ids', type=int)
@@ -891,9 +898,9 @@ def request_verification(issue_id):
flash('Access denied.', 'danger')
return redirect(url_for('issues.index'))
# Only the assignee, director, or admin can request verification
# Only the assignee, director, admin, or auditor can request verification
can_act = (
current_user.role in ['admin', 'director']
current_user.role in ['admin', 'director', 'auditor']
or issue.assigned_to == current_user.id
)
if not can_act:
@@ -936,7 +943,7 @@ def request_verification(issue_id):
@bp.route('/verification-queue')
@login_required
@supervisor_required
@issue_manager_required
def verification_queue():
"""Supervisor queue of all issues awaiting verification, grouped by facility."""
from app.models.facility import Facility, Area
@@ -1032,7 +1039,7 @@ def delete(issue_id):
@login_required
def quick_assign(issue_id):
"""Inline assignee update from the issues list — returns JSON."""
if current_user.role not in ('admin', 'director'):
if current_user.role not in ('admin', 'director', 'auditor'):
return jsonify({'ok': False, 'error': 'Permission denied'}), 403
issue = db.session.get(Issue, issue_id)