Aug 5 - Update code to follow up ST - Fix pending items
This commit is contained in:
@@ -644,6 +644,22 @@ def update_inspection(inspection_id):
|
||||
_sched = _resolve_schedule(_sched_id, user)
|
||||
if _sched is not None:
|
||||
inspection.inspection_schedule_id = _sched.id
|
||||
# phase48 parity with the POST path: a schedule created by
|
||||
# "Schedule Follow-up" knows which inspection it answers, so a draft
|
||||
# that only gets its schedule attached here still becomes a properly
|
||||
# linked re-inspection. Never overrides an explicit parent.
|
||||
if not inspection.parent_inspection_id and _sched.parent_inspection_id:
|
||||
inspection.parent_inspection_id = _sched.parent_inspection_id
|
||||
logger.info('API INSPECTIONS | parent inherited from schedule on '
|
||||
'PATCH | schedule=%s | parent=%s | user=%s',
|
||||
_sched.id, _sched.parent_inspection_id, user.username)
|
||||
|
||||
# An explicitly supplied parent still wins, and can be set on the draft
|
||||
# before submit — mirrors the POST handler's field list.
|
||||
if 'parent_inspection_id' in data:
|
||||
_pid = data.get('parent_inspection_id')
|
||||
if isinstance(_pid, int) and db.session.get(Inspection, _pid) is not None:
|
||||
inspection.parent_inspection_id = _pid
|
||||
|
||||
if 'status' in data:
|
||||
inspection.status = data['status']
|
||||
@@ -670,8 +686,41 @@ def update_inspection(inspection_id):
|
||||
if transitioning_to_complete:
|
||||
_fulfill_schedule(inspection)
|
||||
|
||||
# ── Auto-clear follow-up flag on parent ───────────────────────────────
|
||||
# Mirrors the POST handler. This was previously MISSING here, so an iPad
|
||||
# that created a follow-up as a draft and submitted it via PATCH left the
|
||||
# parent flagged forever — the re-inspection happened, but the parent still
|
||||
# showed "Follow-up Inspection Required" and stayed in every manager's
|
||||
# outstanding list. phase48 made that a normal path, since a schedule-started
|
||||
# follow-up is a draft first.
|
||||
#
|
||||
# Same commit-ordering rule as the POST handler: log_action() must fire AFTER
|
||||
# db.session.commit(), because audit.py commits internally and would
|
||||
# otherwise persist the parent's flag change before this inspection's own
|
||||
# changes are committed — a partial state if the main commit then failed.
|
||||
_parent_log_args = None
|
||||
if transitioning_to_complete and inspection.parent_inspection_id:
|
||||
parent_insp = db.session.get(Inspection, inspection.parent_inspection_id)
|
||||
if parent_insp and parent_insp.follow_up_required:
|
||||
parent_insp.follow_up_required = False
|
||||
logger.info(
|
||||
'API INSPECTIONS | follow_up cleared on PATCH | parent_id=%s | '
|
||||
'by_inspection_id=%s | user=%s',
|
||||
parent_insp.id, inspection.id, user.username,
|
||||
)
|
||||
# Snapshot label strings now — ORM objects may be expired after commit.
|
||||
_parent_log_args = (
|
||||
parent_insp.id,
|
||||
f'{parent_insp.template.name} @ {parent_insp.facility.name}',
|
||||
f'follow_up_required=False (cleared by re-inspection '
|
||||
f'#{inspection.id} via mobile API)',
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
if _parent_log_args:
|
||||
log_action(ACTION_UPDATE, 'Inspection', *_parent_log_args)
|
||||
|
||||
# Notify when a draft transitions to completed — mirrors the POST handler.
|
||||
if transitioning_to_complete:
|
||||
score_val = inspection.overall_score
|
||||
|
||||
@@ -340,18 +340,47 @@ def index():
|
||||
if current_user.role == 'customer':
|
||||
abort(403)
|
||||
|
||||
q = InspectionSchedule.query
|
||||
if current_user.role == 'inspector':
|
||||
q = q.filter(InspectionSchedule.inspector_id == current_user.id)
|
||||
# Two tabs (phase51): Pending = schedules still producing occurrences
|
||||
# (active); Completed = closed ones — fulfilled one-times, recurring
|
||||
# schedules past their end date, and manually paused ones. The partition is
|
||||
# exhaustive and non-overlapping on `active`, so every schedule appears in
|
||||
# exactly one tab and none can be lost; the in-row Status badge
|
||||
# (Active / Ended / Paused) disambiguates the closed ones.
|
||||
#
|
||||
# Before this the list was a single table sorted active-first, which meant a
|
||||
# tenant with years of one-time follow-ups buried the handful of live
|
||||
# schedules an inspector actually needed to act on.
|
||||
tab = request.args.get('tab', 'pending')
|
||||
if tab not in ('pending', 'completed'):
|
||||
tab = 'pending'
|
||||
|
||||
base = InspectionSchedule.query
|
||||
# Inspectors see only their own assignments; managers see everything.
|
||||
if current_user.role == 'inspector':
|
||||
base = base.filter(InspectionSchedule.inspector_id == current_user.id)
|
||||
|
||||
# Counts are computed on the same scoped query, so the badges match what the
|
||||
# viewer can actually open.
|
||||
pending_count = base.filter(InspectionSchedule.active.is_(True)).count()
|
||||
completed_count = base.filter(InspectionSchedule.active.is_(False)).count()
|
||||
|
||||
if tab == 'pending':
|
||||
schedules = (base.filter(InspectionSchedule.active.is_(True))
|
||||
.order_by(InspectionSchedule.next_run_at.asc(),
|
||||
InspectionSchedule.name).all())
|
||||
else:
|
||||
# Most recently completed first. A schedule switched off before it ever
|
||||
# ran has a NULL last_completed_at and sorts last under DESC.
|
||||
schedules = (base.filter(InspectionSchedule.active.is_(False))
|
||||
.order_by(InspectionSchedule.last_completed_at.desc(),
|
||||
InspectionSchedule.next_run_at.desc(),
|
||||
InspectionSchedule.name).all())
|
||||
|
||||
schedules = q.order_by(
|
||||
InspectionSchedule.active.desc(),
|
||||
InspectionSchedule.next_run_at.asc(),
|
||||
InspectionSchedule.name,
|
||||
).all()
|
||||
now = now_eastern()
|
||||
return render_template('inspection_schedules/index.html',
|
||||
schedules=schedules, now=now, today=now.date())
|
||||
schedules=schedules, now=now, today=now.date(),
|
||||
tab=tab, pending_count=pending_count,
|
||||
completed_count=completed_count)
|
||||
|
||||
|
||||
def _form_choices():
|
||||
|
||||
@@ -23,8 +23,27 @@
|
||||
{% 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">
|
||||
<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">
|
||||
@@ -155,13 +174,29 @@
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card shadow-sm">
|
||||
<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 current_user.role != 'inspector' %}
|
||||
<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 %}
|
||||
|
||||
Reference in New Issue
Block a user