Jul 8 - Update scheduled inspection 2

This commit is contained in:
2026-07-08 15:51:18 -04:00
parent 4d661e9776
commit 3cee604abf
2 changed files with 96 additions and 3 deletions
+29 -2
View File
@@ -23,6 +23,7 @@ from app import db
from app.models.scheduled_inspection import ScheduledInspection from app.models.scheduled_inspection import ScheduledInspection
from app.models.facility import Facility from app.models.facility import Facility
from app.models.inspection import Inspection, InspectionTemplate from app.models.inspection import Inspection, InspectionTemplate
from app.models.project import Project
from app.models.user import User from app.models.user import User
from app.utils.forms import ScheduledInspectionForm from app.utils.forms import ScheduledInspectionForm
from app.utils.decorators import project_manager_required from app.utils.decorators import project_manager_required
@@ -37,6 +38,9 @@ bp = Blueprint('scheduled_inspections', __name__, url_prefix='/scheduled-inspect
def _populate_choices(form): def _populate_choices(form):
# facility_id choices are ALL active facilities so POST validation passes
# regardless of which contract the UI-only contract selector had chosen
# (CLAUDE.md rule 61). The contract selector narrows the list client-side.
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all() facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
templates = (InspectionTemplate.query templates = (InspectionTemplate.query
.filter_by(active=True).order_by(InspectionTemplate.name).all()) .filter_by(active=True).order_by(InspectionTemplate.name).all())
@@ -47,6 +51,20 @@ def _populate_choices(form):
form.inspector_id.choices = [(u.id, u.display_name) for u in inspectors] form.inspector_id.choices = [(u.id, u.display_name) for u in inspectors]
def _active_contracts():
return Project.query.filter_by(active=True).order_by(Project.name).all()
def _selected_project_id(form):
"""Contract of the submitted facility (for restoring the selector on
re-render), or None."""
if form.facility_id.data:
fac = db.session.get(Facility, form.facility_id.data)
if fac:
return fac.project_id
return None
# ── List ────────────────────────────────────────────────────────────────────── # ── List ──────────────────────────────────────────────────────────────────────
@bp.route('/') @bp.route('/')
@@ -103,7 +121,9 @@ def create():
return redirect(url_for('scheduled_inspections.index')) return redirect(url_for('scheduled_inspections.index'))
return render_template('scheduled_inspections/form.html', return render_template('scheduled_inspections/form.html',
form=form, title='New Scheduled Inspection') form=form, title='New Scheduled Inspection',
projects=_active_contracts(),
selected_project_id=_selected_project_id(form))
# ── Edit ────────────────────────────────────────────────────────────────────── # ── Edit ──────────────────────────────────────────────────────────────────────
@@ -133,8 +153,15 @@ def edit(schedule_id):
flash('Scheduled inspection updated.', 'success') flash('Scheduled inspection updated.', 'success')
return redirect(url_for('scheduled_inspections.index')) return redirect(url_for('scheduled_inspections.index'))
# Restore the contract selector: submitted facility's contract on error,
# otherwise the schedule's current facility's contract.
sel_pid = _selected_project_id(form)
if sel_pid is None and sched.facility:
sel_pid = sched.facility.project_id
return render_template('scheduled_inspections/form.html', return render_template('scheduled_inspections/form.html',
form=form, title='Edit Scheduled Inspection', schedule=sched) form=form, title='Edit Scheduled Inspection', schedule=sched,
projects=_active_contracts(),
selected_project_id=sel_pid)
# ── Delete ──────────────────────────────────────────────────────────────────── # ── Delete ────────────────────────────────────────────────────────────────────
+67 -1
View File
@@ -10,9 +10,21 @@
<form method="POST" novalidate> <form method="POST" novalidate>
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{# Contract selector — UI only; narrows the facility list via AJAX #}
<div class="mb-3">
<label class="form-label fw-semibold" for="contract_select">Contract</label>
<select id="contract_select" class="form-select">
<option value="">— Select Contract —</option>
{% for p in projects %}
<option value="{{ p.id }}">{{ p.name }}</option>
{% endfor %}
</select>
</div>
{# Facility — populated by JS once a contract is chosen #}
<div class="mb-3"> <div class="mb-3">
{{ form.facility_id.label(class="form-label fw-semibold") }} {{ form.facility_id.label(class="form-label fw-semibold") }}
{{ form.facility_id(class="form-select") }} {{ form.facility_id(class="form-select", id="facility_id") }}
{% 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>
@@ -65,3 +77,57 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %}
<script>
(function () {
'use strict';
var contractSel = document.getElementById('contract_select');
var facilitySel = document.getElementById('facility_id');
if (!contractSel || !facilitySel) { return; }
var FACILITIES_URL = '{{ url_for("inspections.facilities_for_project", project_id=0) }}'.replace('/0', '/');
var preProjectId = {{ selected_project_id | tojson }};
var preFacilityId = {{ form.facility_id.data | tojson }};
function setPlaceholder() {
facilitySel.innerHTML = '<option value="">— Select a Contract first —</option>';
facilitySel.disabled = true;
}
function loadFacilities(projectId, restoreFacilityId) {
facilitySel.disabled = true;
facilitySel.innerHTML = '<option value="">Loading…</option>';
fetch(FACILITIES_URL + projectId)
.then(function (r) { return r.json(); })
.then(function (data) {
facilitySel.innerHTML = '<option value="">— Select Facility —</option>';
data.forEach(function (f) {
var opt = document.createElement('option');
opt.value = f.id;
opt.textContent = f.name;
if (restoreFacilityId && f.id === restoreFacilityId) { opt.selected = true; }
facilitySel.appendChild(opt);
});
facilitySel.disabled = false;
})
.catch(function () {
facilitySel.innerHTML = '<option value="">Could not load facilities</option>';
});
}
contractSel.addEventListener('change', function () {
if (this.value) { loadFacilities(this.value, null); }
else { setPlaceholder(); }
});
// Initial state: restore the contract + facility on edit / validation error.
if (preProjectId) {
contractSel.value = String(preProjectId);
loadFacilities(preProjectId, preFacilityId);
} else {
setPlaceholder();
}
}());
</script>
{% endblock %}