diff --git a/app/routes/facilities.py b/app/routes/facilities.py index 957c009..56049d6 100644 --- a/app/routes/facilities.py +++ b/app/routes/facilities.py @@ -166,7 +166,16 @@ def delete_area(area_id): if area.inspections.count() > 0: flash('Cannot delete area with existing inspections.', 'danger') return redirect(url_for('facilities.view_facility', facility_id=facility_id)) - + + issue_count = area.issues.count() + if issue_count > 0: + flash( + f'Cannot delete area "{area.name}" — it has {issue_count} issue record(s) on file. ' + f'Resolve or reassign those issues first.', + 'danger' + ) + return redirect(url_for('facilities.view_facility', facility_id=facility_id)) + area_name = area.name area_id_snap = area.id db.session.delete(area) diff --git a/app/routes/issues.py b/app/routes/issues.py index 319644e..342ae4c 100644 --- a/app/routes/issues.py +++ b/app/routes/issues.py @@ -1,3 +1,4 @@ +import os from app.utils.time_utils import now_eastern from flask import (Blueprint, render_template, redirect, url_for, flash, request, current_app, jsonify) @@ -549,4 +550,55 @@ def verification_queue(): total_pending = len(pending), sla_status = sla_status, sla_hours_remaining = sla_hours_remaining, - ) \ No newline at end of file + ) + +# ── Delete ──────────────────────────────────────────────────────────────────── + +@bp.route('//delete', methods=['POST']) +@login_required +@supervisor_required +def delete(issue_id): + """Permanently delete an issue and its associated photos. + + Restricted to admin and supervisor roles. The deletion is recorded in + the audit log before the record is removed so there is always a trace. + """ + issue = Issue.query.get_or_404(issue_id) + + # Snapshot fields needed for logging before deletion + issue_id_snap = issue.id + issue_desc = issue.description[:80] + area_name = issue.area.name if issue.area else f'area_id={issue.area_id}' + facility_name = issue.area.facility.name if issue.area else '—' + severity = issue.severity + + # Collect photo paths to clean up from disk after DB delete + photo_paths = [] + if issue.photo_path: + photo_paths.append(issue.photo_path) + if issue.result_photos: + photo_paths.extend(issue.result_photos) + + db.session.delete(issue) + db.session.commit() + + # Remove orphaned photo files — best-effort, never block on failure + static_folder = current_app.root_path + for rel_path in photo_paths: + abs_path = os.path.normpath(os.path.join(static_folder, 'static', rel_path)) + try: + if os.path.isfile(abs_path): + os.remove(abs_path) + except OSError: + pass + + current_app.logger.info( + 'ISSUE DELETED | id=%s | severity=%s | area=%s | facility=%s | deleted_by=%s', + issue_id_snap, severity, area_name, facility_name, current_user.username, + ) + log_action(ACTION_DELETE, 'Issue', issue_id_snap, + f'#{issue_id_snap} {severity} in {area_name}', + f'facility={facility_name}; description={issue_desc}') + + flash(f'Issue #{issue_id_snap} has been permanently deleted.', 'success') + return redirect(url_for('issues.index')) \ No newline at end of file diff --git a/app/templates/facilities/list.html b/app/templates/facilities/list.html index 1bcc30f..dc6081f 100644 --- a/app/templates/facilities/list.html +++ b/app/templates/facilities/list.html @@ -97,7 +97,7 @@ Cancel
- + diff --git a/app/templates/facilities/view.html b/app/templates/facilities/view.html index 67aede2..cd15d0a 100644 --- a/app/templates/facilities/view.html +++ b/app/templates/facilities/view.html @@ -130,9 +130,16 @@ - - -
@@ -185,7 +192,7 @@ Cancel
- + +
+ {% endif %} {% endfor %} diff --git a/app/templates/issues/view.html b/app/templates/issues/view.html index afc9623..9e97058 100644 --- a/app/templates/issues/view.html +++ b/app/templates/issues/view.html @@ -220,9 +220,54 @@ - - Back to Issues - +
+ + Back to Issues + + {% if current_user.role in ['admin', 'supervisor'] %} + + {% endif %} +
+ +{% if current_user.role in ['admin', 'supervisor'] %} + + +{% endif %} + {% block extra_js %}