06/09 Add Inspecter filters, and allow customer to flag issue
This commit is contained in:
@@ -232,6 +232,11 @@ def index():
|
|||||||
facility_filter = request.args.get('facility_id', '')
|
facility_filter = request.args.get('facility_id', '')
|
||||||
follow_up_filter = request.args.get('follow_up', '')
|
follow_up_filter = request.args.get('follow_up', '')
|
||||||
contract_filter = request.args.get('contract_id', '')
|
contract_filter = request.args.get('contract_id', '')
|
||||||
|
date_from_filter = request.args.get('date_from', '')
|
||||||
|
date_to_filter = request.args.get('date_to', '')
|
||||||
|
score_min_filter = request.args.get('score_min', '')
|
||||||
|
score_max_filter = request.args.get('score_max', '')
|
||||||
|
inspector_filter = request.args.get('inspector_id', '')
|
||||||
|
|
||||||
if status_filter:
|
if status_filter:
|
||||||
q = q.filter(Inspection.status == status_filter)
|
q = q.filter(Inspection.status == status_filter)
|
||||||
@@ -247,6 +252,29 @@ def index():
|
|||||||
Inspection.follow_up_required == True,
|
Inspection.follow_up_required == True,
|
||||||
Inspection.status == 'completed',
|
Inspection.status == 'completed',
|
||||||
).filter(~Inspection.follow_ups.any())
|
).filter(~Inspection.follow_ups.any())
|
||||||
|
if date_from_filter:
|
||||||
|
try:
|
||||||
|
q = q.filter(Inspection.inspection_date >= datetime.strptime(date_from_filter, '%Y-%m-%d'))
|
||||||
|
except ValueError:
|
||||||
|
date_from_filter = ''
|
||||||
|
if date_to_filter:
|
||||||
|
try:
|
||||||
|
_dt = datetime.strptime(date_to_filter, '%Y-%m-%d').replace(hour=23, minute=59, second=59)
|
||||||
|
q = q.filter(Inspection.inspection_date <= _dt)
|
||||||
|
except ValueError:
|
||||||
|
date_to_filter = ''
|
||||||
|
if score_min_filter:
|
||||||
|
try:
|
||||||
|
q = q.filter(Inspection.overall_score >= float(score_min_filter))
|
||||||
|
except ValueError:
|
||||||
|
score_min_filter = ''
|
||||||
|
if score_max_filter:
|
||||||
|
try:
|
||||||
|
q = q.filter(Inspection.overall_score <= float(score_max_filter))
|
||||||
|
except ValueError:
|
||||||
|
score_max_filter = ''
|
||||||
|
if inspector_filter.isdigit() and current_user.role != 'inspector':
|
||||||
|
q = q.filter(Inspection.inspector_id == int(inspector_filter))
|
||||||
|
|
||||||
inspections = q.paginate(page=page, per_page=20, error_out=False)
|
inspections = q.paginate(page=page, per_page=20, error_out=False)
|
||||||
|
|
||||||
@@ -276,14 +304,28 @@ def index():
|
|||||||
else:
|
else:
|
||||||
projects = Project.query.filter_by(active=True).order_by(Project.name).all()
|
projects = Project.query.filter_by(active=True).order_by(Project.name).all()
|
||||||
|
|
||||||
|
# Inspector dropdown — shown to all roles except inspector (they only see their own)
|
||||||
|
if current_user.role != 'inspector':
|
||||||
|
inspectors = (User.query
|
||||||
|
.filter(User.role == 'inspector', User.active == True)
|
||||||
|
.order_by(User.full_name, User.username).all())
|
||||||
|
else:
|
||||||
|
inspectors = []
|
||||||
|
|
||||||
return render_template('inspections/list.html',
|
return render_template('inspections/list.html',
|
||||||
inspections=inspections,
|
inspections=inspections,
|
||||||
facilities=facilities,
|
facilities=facilities,
|
||||||
projects=projects,
|
projects=projects,
|
||||||
|
inspectors=inspectors,
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
facility_filter=facility_filter,
|
facility_filter=facility_filter,
|
||||||
follow_up_filter=follow_up_filter,
|
follow_up_filter=follow_up_filter,
|
||||||
contract_filter=contract_filter,
|
contract_filter=contract_filter,
|
||||||
|
date_from_filter=date_from_filter,
|
||||||
|
date_to_filter=date_to_filter,
|
||||||
|
score_min_filter=score_min_filter,
|
||||||
|
score_max_filter=score_max_filter,
|
||||||
|
inspector_filter=inspector_filter,
|
||||||
now=now_eastern())
|
now=now_eastern())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+19
-2
@@ -478,10 +478,27 @@ def unfollow(issue_id):
|
|||||||
|
|
||||||
@bp.route('/new', methods=['GET', 'POST'])
|
@bp.route('/new', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@supervisor_required
|
|
||||||
def create():
|
def create():
|
||||||
from app.models.project import Project
|
if current_user.role not in ('admin', 'director', 'customer'):
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
from app.models.project import Project, CustomerAssignment
|
||||||
form = IssueForm()
|
form = IssueForm()
|
||||||
|
|
||||||
|
if current_user.role == 'customer':
|
||||||
|
cids = get_customer_scope(current_user) or []
|
||||||
|
facilities = (Facility.query
|
||||||
|
.filter(Facility.id.in_(cids), Facility.active == True)
|
||||||
|
.order_by(Facility.name).all()) if cids else []
|
||||||
|
assigned_pids = {
|
||||||
|
a.project_id for a in
|
||||||
|
CustomerAssignment.query.filter_by(user_id=current_user.id).all()
|
||||||
|
}
|
||||||
|
projects = Project.query.filter(
|
||||||
|
Project.active == True, Project.id.in_(assigned_pids)
|
||||||
|
).order_by(Project.name).all()
|
||||||
|
staff = []
|
||||||
|
else:
|
||||||
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
|
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
|
||||||
projects = Project.query.filter_by(active=True).order_by(Project.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_(['admin', 'director', 'inspector'])).order_by(User.username).all()
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
{# Filters #}
|
{# Filters #}
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="card-body py-2">
|
<div class="card-body py-2">
|
||||||
<form method="get" class="row g-2 align-items-end">
|
<form method="get">
|
||||||
|
<div class="row g-2 align-items-end">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<label class="form-label small mb-1">Status</label>
|
<label class="form-label small mb-1">Status</label>
|
||||||
<select name="status" class="form-select form-select-sm">
|
<select name="status" class="form-select form-select-sm">
|
||||||
@@ -41,10 +42,46 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
{% if inspectors %}
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label small mb-1">Inspector</label>
|
||||||
|
<select name="inspector_id" class="form-select form-select-sm">
|
||||||
|
<option value="">All Inspectors</option>
|
||||||
|
{% for u in inspectors %}
|
||||||
|
<option value="{{ u.id }}" {% if inspector_filter == u.id|string %}selected{% endif %}>{{ u.display_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row g-2 align-items-end mt-1">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label small mb-1">Date From</label>
|
||||||
|
<input type="date" name="date_from" class="form-control form-control-sm"
|
||||||
|
value="{{ date_from_filter }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label small mb-1">Date To</label>
|
||||||
|
<input type="date" name="date_to" class="form-control form-control-sm"
|
||||||
|
value="{{ date_to_filter }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
<label class="form-label small mb-1">Min Score</label>
|
||||||
|
<input type="number" name="score_min" class="form-control form-control-sm"
|
||||||
|
min="0" max="100" placeholder="0"
|
||||||
|
value="{{ score_min_filter }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
<label class="form-label small mb-1">Max Score</label>
|
||||||
|
<input type="number" name="score_max" class="form-control form-control-sm"
|
||||||
|
min="0" max="100" placeholder="100"
|
||||||
|
value="{{ score_max_filter }}">
|
||||||
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button type="submit" class="btn btn-sm btn-outline-primary">Filter</button>
|
<button type="submit" class="btn btn-sm btn-outline-primary">Filter</button>
|
||||||
<a href="{{ url_for('inspections.index') }}" class="btn btn-sm btn-outline-secondary">Clear</a>
|
<a href="{{ url_for('inspections.index') }}" class="btn btn-sm btn-outline-secondary">Clear</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,7 +162,7 @@
|
|||||||
{% for p in inspections.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
|
{% for p in inspections.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
|
||||||
{% if p %}
|
{% if p %}
|
||||||
<li class="page-item {{ 'active' if p == inspections.page }}">
|
<li class="page-item {{ 'active' if p == inspections.page }}">
|
||||||
<a class="page-link" href="{{ url_for('inspections.index', page=p, status=status_filter, contract_id=contract_filter, facility_id=facility_filter) }}">{{ p }}</a>
|
<a class="page-link" href="{{ url_for('inspections.index', page=p, status=status_filter, contract_id=contract_filter, facility_id=facility_filter, date_from=date_from_filter, date_to=date_to_filter, score_min=score_min_filter, score_max=score_max_filter, inspector_id=inspector_filter) }}">{{ p }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled"><span class="page-link">…</span></li>
|
<li class="page-item disabled"><span class="page-link">…</span></li>
|
||||||
|
|||||||
@@ -29,14 +29,21 @@
|
|||||||
{% for e in form.facility_id.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
{% for e in form.facility_id.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Remaining fields #}
|
{# Remaining fields — assigned_to hidden from customer role #}
|
||||||
{% for field in [form.severity, form.description, form.photo, form.assigned_to] %}
|
{% for field in [form.severity, form.description, form.photo] %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ field.label(class="form-label fw-semibold") }}
|
{{ field.label(class="form-label fw-semibold") }}
|
||||||
{{ field(class="form-select" if field.type == 'SelectField' else "form-control", rows=4 if field.type == 'TextAreaField' else none) }}
|
{{ field(class="form-select" if field.type == 'SelectField' else "form-control", rows=4 if field.type == 'TextAreaField' else none) }}
|
||||||
{% for e in field.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
{% for e in field.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if current_user.role != 'customer' %}
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ form.assigned_to.label(class="form-label fw-semibold") }}
|
||||||
|
{{ form.assigned_to(class="form-select") }}
|
||||||
|
{% for e in form.assigned_to.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="submit" class="btn btn-danger">Log Issue</button>
|
<button type="submit" class="btn btn-danger">Log Issue</button>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-exclamation-triangle"></i> Issues</h2>
|
<h2><i class="bi bi-exclamation-triangle"></i> Issues</h2>
|
||||||
{% if current_user.role in ['admin','director'] %}
|
{% if current_user.role in ['admin','director','customer'] %}
|
||||||
<a href="{{ url_for('issues.create') }}" class="btn btn-danger">
|
<a href="{{ url_for('issues.create') }}" class="btn btn-danger">
|
||||||
<i class="bi bi-plus-circle"></i> Log Issue
|
<i class="bi bi-plus-circle"></i> Log Issue
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user