March 17 2026: form editor - update functions

This commit is contained in:
2026-03-17 15:28:54 -04:00
parent a157d05139
commit f0d284a609
5 changed files with 900 additions and 63 deletions
+70
View File
@@ -180,6 +180,16 @@ def index():
for r in perf_rows
]
# ── Facilities list for the trend-by-facility chart selector ────────────
if is_privileged or is_project_manager:
all_facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
elif is_customer and customer_facility_ids:
all_facilities = Facility.query.filter(
Facility.id.in_(customer_facility_ids), Facility.active == True
).order_by(Facility.name).all()
else:
all_facilities = []
return render_template(
'dashboard.html',
today_inspections = today_inspections,
@@ -197,4 +207,64 @@ def index():
facility_perf = facility_perf,
customer_facilities = customer_facilities,
pending_followups = pending_followups,
all_facilities = all_facilities,
)
# ── AJAX: facility score trend ────────────────────────────────────────────────
@bp.route('/facility-trend')
@login_required
def facility_trend():
"""Return daily avg-score data for a single facility over N days.
Query params:
facility_id (int, required)
days (int, default 30 — allowed: 30, 60, 90)
Response JSON:
{ labels: ['2026-03-01', ...], data: [85.2, ...], facility: 'Name' }
"""
from flask import jsonify, request as req
facility_id = req.args.get('facility_id', type=int)
days = req.args.get('days', 30, type=int)
if days not in (30, 60, 90):
days = 30
if not facility_id:
return jsonify({'labels': [], 'data': [], 'facility': ''})
# Scope check for customer users
if current_user.role == 'customer':
cids = get_customer_scope(current_user) or []
if facility_id not in cids:
return jsonify({'labels': [], 'data': [], 'facility': ''}), 403
facility = Facility.query.get(facility_id)
if not facility:
return jsonify({'labels': [], 'data': [], 'facility': ''})
start = now_eastern() - timedelta(days=days)
rows = (
db.session.query(
func.date(Inspection.inspection_date).label('day'),
func.avg(Inspection.overall_score).label('avg'),
)
.filter(
Inspection.facility_id == facility_id,
Inspection.status == 'completed',
Inspection.overall_score.isnot(None),
Inspection.inspection_date >= start,
)
.group_by(func.date(Inspection.inspection_date))
.order_by(func.date(Inspection.inspection_date))
.all()
)
return jsonify({
'labels': [str(r.day) for r in rows],
'data': [round(float(r.avg), 2) for r in rows],
'facility': facility.name,
})
+70
View File
@@ -230,6 +230,22 @@ def _send_report(report: ScheduledReport):
fname = f'jqc_report_{report.frequency}_{start.strftime("%Y%m%d")}.csv'
msg.attach(fname, 'text/csv', csv_bytes)
if report.include_pdf:
try:
from app.utils.pdf_export import generate_scheduled_report_pdf
pdf_bytes = generate_scheduled_report_pdf(
report_name = report.name,
frequency = report.frequency,
start = start,
end = end,
facility_name = report.facility.name if report.facility else None,
data = data,
)
pdf_fname = f'jqc_report_{report.frequency}_{start.strftime("%Y%m%d")}.pdf'
msg.attach(pdf_fname, 'application/pdf', pdf_bytes)
except Exception as exc:
logger.error('SCHEDULED REPORT PDF FAILED | id=%s | error=%s', report.id, exc)
try:
mail.send(msg)
logger.info('SCHEDULED REPORT SENT | id=%s | name=%r | recipients=%s',
@@ -340,6 +356,60 @@ def delete(report_id):
return redirect(url_for('scheduled_reports.index'))
@bp.route('/<int:report_id>/preview')
@login_required
@supervisor_required
def preview(report_id):
"""Render the scheduled report email in-browser for review."""
report = ScheduledReport.query.get_or_404(report_id)
start, end = _date_window(report.frequency)
data = _build_report_data(report, start, end)
base_url = current_app.config.get('APP_BASE_URL', '').rstrip('/')
current_app.logger.info(
'SCHEDULED REPORT PREVIEW | id=%s | name=%r | by=%s',
report.id, report.name, current_user.username,
)
return render_template('scheduled_reports/email.html',
base_url=base_url, **data)
@bp.route('/<int:report_id>/preview-pdf')
@login_required
@supervisor_required
def preview_pdf(report_id):
"""Generate and stream the PDF attachment for in-browser review."""
from flask import Response
from app.utils.pdf_export import generate_scheduled_report_pdf
report = ScheduledReport.query.get_or_404(report_id)
start, end = _date_window(report.frequency)
data = _build_report_data(report, start, end)
pdf_bytes = generate_scheduled_report_pdf(
report_name = report.name,
frequency = report.frequency,
start = start,
end = end,
facility_name = report.facility.name if report.facility else None,
data = data,
)
filename = f'jqc_report_{report.frequency}_{start.strftime("%Y%m%d")}.pdf'
current_app.logger.info(
'SCHEDULED REPORT PDF PREVIEW | id=%s | name=%r | by=%s',
report.id, report.name, current_user.username,
)
return Response(
pdf_bytes,
mimetype='application/pdf',
headers={'Content-Disposition': f'inline; filename="{filename}"'},
)
@bp.route('/<int:report_id>/send-now', methods=['POST'])
@login_required
@supervisor_required