05/11 Update: add contract selection when start an inspection
This commit is contained in:
@@ -10,6 +10,7 @@ from app import db
|
|||||||
from app.models.inspection import (Inspection, InspectionTemplate,
|
from app.models.inspection import (Inspection, InspectionTemplate,
|
||||||
ChecklistItem, InspectionResult)
|
ChecklistItem, InspectionResult)
|
||||||
from app.models.facility import Facility, Area
|
from app.models.facility import Facility, Area
|
||||||
|
from app.models.project import Project
|
||||||
from app.models.issue import Issue
|
from app.models.issue import Issue
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.utils.forms import StartInspectionForm, IssueForm
|
from app.utils.forms import StartInspectionForm, IssueForm
|
||||||
@@ -230,17 +231,38 @@ def start():
|
|||||||
form = StartInspectionForm()
|
form = StartInspectionForm()
|
||||||
|
|
||||||
templates = InspectionTemplate.query.order_by(InspectionTemplate.name).all()
|
templates = InspectionTemplate.query.order_by(InspectionTemplate.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()
|
||||||
|
|
||||||
form.template_id.choices = [(t.id, t.name) for t in templates]
|
form.template_id.choices = [(t.id, t.name) for t in templates]
|
||||||
form.facility_id.choices = [(f.id, f.name) for f in facilities]
|
form.project_id.choices = [(p.id, p.name) for p in projects]
|
||||||
|
|
||||||
|
# Seed facility choices: use submitted project_id, session value, or first project
|
||||||
from flask import session as _session
|
from flask import session as _session
|
||||||
|
if form.is_submitted():
|
||||||
|
selected_project_id = form.project_id.data
|
||||||
|
elif _session.get('reinspect_facility_id'):
|
||||||
|
# Derive project from the reinspect facility
|
||||||
|
_rf = db.session.get(Facility, _session['reinspect_facility_id'])
|
||||||
|
selected_project_id = _rf.project_id if _rf and _rf.project_id else (projects[0].id if projects else None)
|
||||||
|
else:
|
||||||
|
selected_project_id = projects[0].id if projects else None
|
||||||
|
|
||||||
|
if selected_project_id:
|
||||||
|
facilities = Facility.query.filter_by(active=True, project_id=selected_project_id).order_by(Facility.name).all()
|
||||||
|
else:
|
||||||
|
facilities = []
|
||||||
|
|
||||||
|
form.facility_id.choices = [(f.id, f.name) for f in facilities]
|
||||||
|
if not form.facility_id.choices:
|
||||||
|
form.facility_id.choices = [('', '— no facilities —')]
|
||||||
|
|
||||||
if not form.is_submitted():
|
if not form.is_submitted():
|
||||||
if _session.get('reinspect_template_id'):
|
if _session.get('reinspect_template_id'):
|
||||||
form.template_id.data = _session['reinspect_template_id']
|
form.template_id.data = _session['reinspect_template_id']
|
||||||
if _session.get('reinspect_facility_id'):
|
if _session.get('reinspect_facility_id'):
|
||||||
form.facility_id.data = _session['reinspect_facility_id']
|
form.facility_id.data = _session['reinspect_facility_id']
|
||||||
|
if selected_project_id:
|
||||||
|
form.project_id.data = selected_project_id
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
template = db.session.get(InspectionTemplate, form.template_id.data)
|
template = db.session.get(InspectionTemplate, form.template_id.data)
|
||||||
@@ -272,7 +294,7 @@ def start():
|
|||||||
flash('Inspection started. Fill in the form below and submit when complete.', 'info')
|
flash('Inspection started. Fill in the form below and submit when complete.', 'info')
|
||||||
return redirect(url_for('inspections.execute', inspection_id=inspection.id))
|
return redirect(url_for('inspections.execute', inspection_id=inspection.id))
|
||||||
|
|
||||||
return render_template('inspections/start.html', form=form, facilities=facilities)
|
return render_template('inspections/start.html', form=form, projects=projects)
|
||||||
|
|
||||||
|
|
||||||
# ── AJAX: areas for a given facility ─────────────────────────────────────────
|
# ── AJAX: areas for a given facility ─────────────────────────────────────────
|
||||||
@@ -284,6 +306,18 @@ def areas_for_facility(facility_id):
|
|||||||
return jsonify([{'id': a.id, 'name': a.name} for a in areas])
|
return jsonify([{'id': a.id, 'name': a.name} for a in areas])
|
||||||
|
|
||||||
|
|
||||||
|
# ── AJAX: facilities for a given project/contract ────────────────────────────
|
||||||
|
|
||||||
|
@bp.route('/facilities_for_project/<int:project_id>')
|
||||||
|
@login_required
|
||||||
|
def facilities_for_project(project_id):
|
||||||
|
facilities = (Facility.query
|
||||||
|
.filter_by(active=True, project_id=project_id)
|
||||||
|
.order_by(Facility.name)
|
||||||
|
.all())
|
||||||
|
return jsonify([{'id': f.id, 'name': f.name} for f in facilities])
|
||||||
|
|
||||||
|
|
||||||
# ── Execute ───────────────────────────────────────────────────────────────────
|
# ── Execute ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/<int:inspection_id>/execute', methods=['GET', 'POST'])
|
@bp.route('/<int:inspection_id>/execute', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -17,9 +17,22 @@
|
|||||||
{% for e in form.template_id.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
|
{% for e in form.template_id.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ form.project_id.label(class="form-label fw-semibold") }}
|
||||||
|
{{ form.project_id(class="form-select" + (" is-invalid" if form.project_id.errors else ""), id="projectSelect") }}
|
||||||
|
{% for e in form.project_id.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<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", id="facilitySelect") }}
|
{{ form.facility_id(class="form-select" + (" is-invalid" if form.facility_id.errors else ""), id="facilitySelect") }}
|
||||||
|
{% for e in form.facility_id.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
|
||||||
|
<div id="facilitySpinner" class="form-text text-muted d-none">
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status"></span> Loading facilities…
|
||||||
|
</div>
|
||||||
|
<div id="facilityEmpty" class="form-text text-warning d-none">
|
||||||
|
No active facilities found for this contract.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
@@ -32,4 +45,61 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const projectSel = document.getElementById('projectSelect');
|
||||||
|
const facilitySel = document.getElementById('facilitySelect');
|
||||||
|
const spinner = document.getElementById('facilitySpinner');
|
||||||
|
const emptyMsg = document.getElementById('facilityEmpty');
|
||||||
|
|
||||||
|
function loadFacilities(projectId, selectedFacilityId) {
|
||||||
|
if (!projectId) return;
|
||||||
|
spinner.classList.remove('d-none');
|
||||||
|
emptyMsg.classList.add('d-none');
|
||||||
|
facilitySel.disabled = true;
|
||||||
|
|
||||||
|
fetch(`{{ url_for('inspections.facilities_for_project', project_id=0) }}`.replace('/0', '/' + projectId))
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
facilitySel.innerHTML = '';
|
||||||
|
if (data.length === 0) {
|
||||||
|
emptyMsg.classList.remove('d-none');
|
||||||
|
facilitySel.innerHTML = '<option value="">— no facilities —</option>';
|
||||||
|
} else {
|
||||||
|
data.forEach(f => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = f.id;
|
||||||
|
opt.textContent = f.name;
|
||||||
|
if (selectedFacilityId && f.id === selectedFacilityId) opt.selected = true;
|
||||||
|
facilitySel.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
facilitySel.innerHTML = '<option value="">— error loading facilities —</option>';
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
spinner.classList.add('d-none');
|
||||||
|
facilitySel.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
projectSel.addEventListener('change', function () {
|
||||||
|
loadFacilities(this.value, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// On page load: if project already selected (e.g. validation error or reinspect),
|
||||||
|
// reload facility list preserving the currently selected facility value.
|
||||||
|
const initProject = projectSel.value;
|
||||||
|
const initFacility = facilitySel.value ? parseInt(facilitySel.value, 10) : null;
|
||||||
|
if (initProject) {
|
||||||
|
// Only reload if facility list looks like a placeholder (no real numeric id in options)
|
||||||
|
const hasRealOptions = Array.from(facilitySel.options).some(o => parseInt(o.value, 10) > 0);
|
||||||
|
if (!hasRealOptions) {
|
||||||
|
loadFacilities(initProject, initFacility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -142,6 +142,7 @@ class ChecklistItemForm(FlaskForm):
|
|||||||
|
|
||||||
class StartInspectionForm(FlaskForm):
|
class StartInspectionForm(FlaskForm):
|
||||||
template_id = SelectField('Template', coerce=int, validators=[DataRequired()])
|
template_id = SelectField('Template', coerce=int, validators=[DataRequired()])
|
||||||
|
project_id = SelectField('Contract', coerce=int, validators=[DataRequired()])
|
||||||
facility_id = SelectField('Facility', coerce=int, validators=[DataRequired()])
|
facility_id = SelectField('Facility', coerce=int, validators=[DataRequired()])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user