Files

222 lines
11 KiB
HTML

{% extends "base.html" %}
{% block title %}Inspection Schedules{% endblock %}
{% block content %}
{# Who may create/edit/delete a schedule — mirrors schedule_manager_required
in routes/inspection_schedules.py. 'customer' is the Customer DIRECTOR, who
plans work for their own facilities; a Customer Inspector ('external_inspector')
performs schedules and is covered by is_inspector below. #}
{% set can_manage_schedules = current_user.role in
['admin','director','project_manager','auditor','customer'] %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-calendar2-week"></i> Inspection Schedules</h2>
{# This page is reached from the Inspections list ("Scheduled") and has no
entry of its own in the main nav, so without this button the only way back
is the browser control. #}
<div class="d-flex gap-2">
<a href="{{ url_for('inspections.index') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Inspections
</a>
{% if can_manage_schedules %}
<a href="{{ url_for('inspection_schedules.create') }}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> New Schedule
</a>
{% endif %}
</div>
</div>
<p class="text-muted small mb-4">
{% if current_user.is_inspector %}
Inspections scheduled for you. <strong>Auto</strong> schedules appear in your
Inspections list on their own each period; <strong>Plan</strong> schedules wait
for you to press Start.
{% else %}
<strong>Auto</strong> schedules automatically create an in-progress inspection
for the assigned inspector each period. <strong>Plan</strong> schedules assign a
due date and let the inspector press Start when they begin — with reminders the
day before, on the day, and an alert to managers once overdue.
{% endif %}
</p>
{# Pending / Completed tabs (phase51). The partition is on `active`, so it is
exhaustive — no schedule can fall between the two tabs. #}
<ul class="nav nav-tabs mb-0">
<li class="nav-item">
<a class="nav-link {{ 'active' if tab == 'pending' }}"
href="{{ url_for('inspection_schedules.index', tab='pending') }}">
<i class="bi bi-hourglass-split"></i> Pending
<span class="badge rounded-pill bg-{{ 'primary' if tab == 'pending' else 'secondary' }} ms-1">{{ pending_count }}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if tab == 'completed' }}"
href="{{ url_for('inspection_schedules.index', tab='completed') }}">
<i class="bi bi-check2-circle"></i> Completed
<span class="badge rounded-pill bg-{{ 'primary' if tab == 'completed' else 'secondary' }} ms-1">{{ completed_count }}</span>
</a>
</li>
</ul>
{% if schedules %}
<div class="card shadow-sm border-top-0 rounded-top-0">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Name</th><th>Template</th><th>Facility / Area</th>
<th>Inspector</th><th>Frequency</th><th>Mode</th><th>Next Due</th>
<th>Ends</th><th>Last Run</th><th>Status</th><th width="190"></th>
</tr>
</thead>
<tbody>
{% for s in schedules %}
<tr class="{{ 'text-muted' if not s.active else '' }}">
<td>
<strong>{{ s.name }}</strong>
{% if s.is_follow_up %}
{# phase48 — a schedule planned as the deferred twin of
"Re-inspect Now". Starting it produces a linked re-inspection. #}
<a href="{{ url_for('inspections.view', inspection_id=s.parent_inspection_id) }}"
class="badge bg-warning text-dark text-decoration-none ms-1"
title="Follow-up of inspection #{{ s.parent_inspection_id }}">
<i class="bi bi-arrow-repeat"></i> Follow-up #{{ s.parent_inspection_id }}
</a>
{% endif %}
</td>
<td>{{ s.template.name if s.template else '—' }}</td>
<td>
{{ s.facility.name if s.facility else '—' }}
{% if s.area %}<span class="text-muted small">/ {{ s.area.name }}</span>{% endif %}
</td>
<td>{{ s.inspector.display_name if s.inspector else '—' }}</td>
<td><span class="badge bg-secondary">{{ s.recurrence_label }}</span></td>
<td>
{% if s.mode == 'plan' %}
<span class="badge bg-info text-dark" title="Inspector presses Start">Plan</span>
{% else %}
<span class="badge bg-light text-dark border" title="Cron creates the inspection">Auto</span>
{% endif %}
</td>
<td class="small {{ 'text-danger fw-semibold' if s.active and s.next_run_at and s.next_run_at <= now else 'text-muted' }}">
{{ s.next_run_at.strftime('%Y-%m-%d %H:%M') if s.next_run_at else '—' }}
{% if s.is_overdue(today) %}
<span class="badge bg-danger ms-1">Overdue</span>
{% endif %}
</td>
<td class="small text-muted">
{% if s.frequency == 'once' %}—
{% elif s.end_date %}{{ s.end_date.strftime('%Y-%m-%d') }}
{% else %}No end{% endif %}
</td>
<td class="small text-muted">
{{ s.last_run_at.strftime('%Y-%m-%d %H:%M') if s.last_run_at else 'Never' }}
</td>
<td>
{# "Ended" separates a schedule that reached its end date from one a
manager switched off — both are inactive, for different reasons. #}
{% if s.active %}<span class="badge bg-success">Active</span>
{% elif s.is_expired %}<span class="badge bg-dark">Ended</span>
{% else %}<span class="badge bg-secondary">Paused</span>{% endif %}
{# phase50 — receipt acknowledgement. Only meaningful for plan
mode: an auto schedule materialises itself, so there is no
request for anyone to receive. #}
{% if s.mode == 'plan' and s.inspector_id %}
{% if s.is_acknowledged %}
<span class="badge bg-light text-success border border-success ms-1"
title="Inspector confirmed receipt on {{ s.acknowledged_at.strftime('%b %d, %Y %I:%M %p') }}">
<i class="bi bi-check-circle"></i> Confirmed
</span>
{% elif s.active %}
<span class="badge bg-light text-warning border border-warning ms-1"
title="The assigned inspector has not confirmed receipt yet">
<i class="bi bi-hourglass-split"></i> Awaiting confirmation
</span>
{% endif %}
{% endif %}
</td>
<td class="text-end">
{# Assignee-only, like Start: a manager must not be able to confirm
on someone's behalf, since the record means "this person saw it". #}
{% if s.active and s.mode == 'plan' and not s.is_acknowledged
and s.inspector_id == current_user.id %}
<form method="POST" class="d-inline"
action="{{ url_for('inspection_schedules.acknowledge', schedule_id=s.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-success"
title="Confirm you have received this request">
<i class="bi bi-check-lg"></i> Confirm
</button>
</form>
{% endif %}
{# Start is for whoever must DO the work. A Customer Director can
plan a schedule but never execute one — the route 403s them, so
the button must not be offered either. #}
{% if s.active and s.mode == 'plan'
and current_user.role != 'customer'
and (not current_user.is_inspector or s.inspector_id == current_user.id) %}
<a href="{{ url_for('inspection_schedules.start', schedule_id=s.id) }}"
class="btn btn-sm btn-primary" title="Start this inspection now">
<i class="bi bi-play-fill"></i> Start
</a>
{% endif %}
{% if can_manage_schedules %}
<a href="{{ url_for('inspection_schedules.edit', schedule_id=s.id) }}"
class="btn btn-sm btn-outline-secondary" title="Edit">
<i class="bi bi-pencil"></i>
</a>
<form method="POST"
action="{{ url_for('inspection_schedules.run_now', schedule_id=s.id) }}"
class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-primary" title="Create inspection now"
onclick="return confirm('Create an inspection from this schedule now?')">
<i class="bi bi-play-circle"></i>
</button>
</form>
<form method="POST"
action="{{ url_for('inspection_schedules.delete', schedule_id=s.id) }}"
class="d-inline"
onsubmit="return confirm('Delete this schedule? Existing inspections are not affected.')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete">
<i class="bi bi-trash3"></i>
</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<div class="card shadow-sm border-top-0 rounded-top-0">
<div class="card-body text-center py-5 text-muted">
{% if tab == 'completed' %}
<i class="bi bi-check2-circle fs-1 d-block mb-3 opacity-25"></i>
<p class="mb-0">No completed schedules yet.</p>
{% elif completed_count %}
{# Nothing pending but there IS history — offer the other tab rather than
inviting them to create a duplicate of something already closed. #}
<i class="bi bi-calendar-check fs-1 d-block mb-3 opacity-25"></i>
<p class="mb-3">Nothing pending — all schedules are complete.</p>
<a href="{{ url_for('inspection_schedules.index', tab='completed') }}"
class="btn btn-outline-secondary">
<i class="bi bi-check2-circle"></i> View Completed ({{ completed_count }})
</a>
{% else %}
<i class="bi bi-calendar-x fs-1 d-block mb-3 opacity-25"></i>
<p class="mb-3">No inspection schedules configured yet.</p>
{% if can_manage_schedules %}
<a href="{{ url_for('inspection_schedules.create') }}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Create First Schedule
</a>
{% endif %}
{% endif %}
</div>
</div>
{% endif %}
{% endblock %}