Feb 27 2026: fix Inspection result form

This commit is contained in:
2026-02-27 17:11:52 -05:00
parent 48fa06be8d
commit 1ee621bf58
5 changed files with 96 additions and 17 deletions
+2
View File
@@ -15,6 +15,8 @@ class Issue(db.Model):
assigned_to = db.Column(db.Integer, db.ForeignKey('users.id'))
reported_at = db.Column(db.DateTime, default=datetime.utcnow)
resolved_at = db.Column(db.DateTime)
result_notes = db.Column(db.Text)
result_photos = db.Column(db.JSON) # list of relative paths e.g. ["uploads/issue_photos/abc.jpg"]
# Relationships
assigned_user = db.relationship('User', foreign_keys=[assigned_to], backref='assigned_issues')
+30 -5
View File
@@ -1,6 +1,6 @@
from datetime import datetime
from flask import (Blueprint, render_template, redirect, url_for,
flash, request)
flash, request, current_app)
from flask_login import login_required, current_user
from app import db
from app.models.issue import Issue
@@ -21,11 +21,9 @@ def index():
q = Issue.query.order_by(Issue.reported_at.desc())
# Inspectors only see issues they reported (linked to their inspections)
# Inspectors only see issues assigned to them
if current_user.role == 'inspector':
from app.models.inspection import Inspection
q = q.join(Inspection, Issue.inspection_id == Inspection.id)\
.filter(Inspection.inspector_id == current_user.id)
q = q.filter(Issue.assigned_to == current_user.id)
severity_filter = request.args.get('severity', '')
status_filter = request.args.get('status', '')
@@ -48,6 +46,12 @@ def index():
@login_required
def view(issue_id):
issue = Issue.query.get_or_404(issue_id)
# Access control: inspectors may only view/edit issues assigned to them
if current_user.role == 'inspector' and issue.assigned_to != current_user.id:
flash('Access denied. You can only view issues assigned to you.', 'danger')
return redirect(url_for('issues.index'))
form = IssueUpdateForm(obj=issue)
staff = User.query.filter(User.role.in_(['supervisor','inspector'])).order_by(User.username).all()
@@ -56,6 +60,9 @@ def view(issue_id):
if form.validate_on_submit():
issue.status = form.status.data
# Only admin/supervisor can reassign; inspectors can only update status
if current_user.role in ['admin', 'supervisor']:
issue.assigned_to = form.assigned_to.data or None
if form.status.data == 'resolved' and not issue.resolved_at:
@@ -63,7 +70,25 @@ def view(issue_id):
elif form.status.data != 'resolved':
issue.resolved_at = None
# Save result notes (overwrite with latest value)
issue.result_notes = form.result_notes.data or None
# Append any newly uploaded result photos
from app.routes.inspections import _save_photo
new_photos = []
for file_obj in request.files.getlist('result_photos'):
path = _save_photo(file_obj, subfolder='issue_result_photos')
if path:
new_photos.append(path)
if new_photos:
existing = issue.result_photos or []
issue.result_photos = existing + new_photos
db.session.commit()
current_app.logger.info(
'ISSUE UPDATED | id=%s | status=%s | result_photos_added=%s | updated_by=%s',
issue.id, issue.status, len(new_photos), current_user.username
)
flash('Issue updated.', 'success')
return redirect(url_for('issues.view', issue_id=issue_id))
+9 -1
View File
@@ -71,7 +71,15 @@
{% if issue.assigned_user %}{{ issue.assigned_user.username }}
{% else %}<span class="text-muted"></span>{% endif %}
</td>
<td><a href="{{ url_for('issues.view', issue_id=issue.id) }}" class="btn btn-sm btn-outline-secondary">View</a></td>
<td>
<a href="{{ url_for('issues.view', issue_id=issue.id) }}" class="btn btn-sm btn-outline-secondary">
{% if current_user.role in ['admin','supervisor'] or issue.assigned_to == current_user.id %}
<i class="bi bi-pencil"></i> Edit
{% else %}
<i class="bi bi-eye"></i> View
{% endif %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
+41 -2
View File
@@ -48,29 +48,68 @@
<img src="{{ url_for('static', filename=issue.photo_path) }}" class="img-fluid rounded" style="max-height:300px;">
</a>
{% endif %}
{% if issue.result_notes or issue.result_photos %}
<hr>
<h6><i class="bi bi-clipboard2-check text-success"></i> Resolution Details</h6>
{% if issue.result_notes %}
<p class="mb-2" style="white-space:pre-wrap;">{{ issue.result_notes }}</p>
{% endif %}
{% if issue.result_photos %}
<div class="d-flex flex-wrap gap-2 mt-2">
{% for photo in issue.result_photos %}
<a href="{{ url_for('static', filename=photo) }}" target="_blank">
<img src="{{ url_for('static', filename=photo) }}"
class="rounded border" style="max-height:120px; max-width:160px; object-fit:cover;"
alt="Result photo">
</a>
{% endfor %}
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
<div class="col-lg-4">
{% if current_user.role in ['admin','supervisor'] %}
{% set can_edit = current_user.role in ['admin','supervisor'] or issue.assigned_to == current_user.id %}
{% if can_edit %}
<div class="card shadow-sm">
<div class="card-header bg-light"><h6 class="mb-0">Update Issue</h6></div>
<div class="card-body">
<form method="post">
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
{{ form.status.label(class="form-label fw-semibold") }}
{{ form.status(class="form-select") }}
</div>
{% if current_user.role in ['admin','supervisor'] %}
<div class="mb-3">
{{ form.assigned_to.label(class="form-label fw-semibold") }}
{{ form.assigned_to(class="form-select") }}
</div>
{% endif %}
<div class="mb-3">
{{ form.comments.label(class="form-label fw-semibold") }}
{{ form.comments(class="form-control", rows=3, placeholder="Optional update notes…") }}
</div>
<div class="mb-3">
{{ form.result_notes.label(class="form-label fw-semibold") }}
{{ form.result_notes(class="form-control", rows=3,
placeholder="Describe what was done to resolve this issue…",
value=issue.result_notes or '') }}
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Result Photos</label>
<input type="file" name="result_photos" id="result_photos"
class="form-control" accept="image/*" multiple>
<div class="form-text">Attach one or more photos showing the resolution.</div>
{% if issue.result_photos %}
<div class="mt-2">
<small class="text-muted">{{ issue.result_photos|length }} photo(s) already uploaded</small>
</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary w-100">Save Update</button>
</form>
</div>
+5
View File
@@ -128,3 +128,8 @@ class IssueUpdateForm(FlaskForm):
], validators=[DataRequired()])
assigned_to = SelectField('Assign To', coerce=int, validators=[Optional()])
comments = TextAreaField('Update Notes', validators=[Optional(), Length(max=1000)])
result_notes = TextAreaField('Result Notes', validators=[Optional(), Length(max=2000)])
result_photos = FileField('Result Photos', validators=[
Optional(),
FileAllowed(['jpg','jpeg','png','gif'], 'Images only.')
])