06/12 Add export PDF function for Inspection filter result
This commit is contained in:
+122
-1
@@ -16,7 +16,7 @@ from app.models.issue import Issue
|
||||
from app.models.user import User
|
||||
from app.utils.forms import StartInspectionForm, IssueForm
|
||||
from app.utils.decorators import supervisor_required
|
||||
from app.utils.pdf_export import generate_inspection_pdf
|
||||
from app.utils.pdf_export import generate_inspection_pdf, generate_inspections_list_pdf
|
||||
from app.utils.notifications import notify, notify_customers_for_facility, notify_by_matrix
|
||||
from app.models.notification import (
|
||||
EVENT_INSPECTION_DONE, EVENT_ISSUE_ASSIGNED,
|
||||
@@ -978,6 +978,127 @@ def flag_issue(inspection_id):
|
||||
form=form, inspection=inspection)
|
||||
|
||||
|
||||
# ── Export filtered list to PDF ───────────────────────────────────────────────
|
||||
|
||||
@bp.route('/export-list-pdf')
|
||||
@login_required
|
||||
def export_list_pdf():
|
||||
"""Generate and stream a PDF of the currently filtered inspection list."""
|
||||
q = Inspection.query.options(
|
||||
joinedload(Inspection.facility),
|
||||
joinedload(Inspection.template),
|
||||
joinedload(Inspection.inspector),
|
||||
joinedload(Inspection.area),
|
||||
).order_by(Inspection.inspection_date.desc())
|
||||
|
||||
if current_user.role == 'inspector':
|
||||
fids = get_inspector_scope(current_user)
|
||||
if not fids:
|
||||
q = q.filter(False)
|
||||
else:
|
||||
q = q.filter(Inspection.facility_id.in_(fids))
|
||||
elif current_user.role == 'customer':
|
||||
customer_facility_ids = get_customer_scope(current_user)
|
||||
if not customer_facility_ids:
|
||||
q = q.filter(False)
|
||||
else:
|
||||
q = q.filter(Inspection.facility_id.in_(customer_facility_ids))
|
||||
|
||||
status_filter = request.args.get('status', '')
|
||||
facility_filter = request.args.get('facility_id', '')
|
||||
contract_filter = request.args.get('contract_id', '')
|
||||
date_from_filter = request.args.get('date_from', '')
|
||||
date_to_filter = request.args.get('date_to', '')
|
||||
score_min_filter = request.args.get('score_min', '')
|
||||
score_max_filter = request.args.get('score_max', '')
|
||||
inspector_filter = request.args.get('inspector_id', '')
|
||||
|
||||
if status_filter == 'follow_up':
|
||||
q = q.filter(
|
||||
Inspection.follow_up_required == True,
|
||||
Inspection.status == 'completed',
|
||||
).filter(~Inspection.follow_ups.any())
|
||||
elif status_filter == 'has_issues':
|
||||
from sqlalchemy import exists as sa_exists
|
||||
q = q.filter(sa_exists().where(Issue.inspection_id == Inspection.id))
|
||||
elif status_filter:
|
||||
q = q.filter(Inspection.status == status_filter)
|
||||
if contract_filter.isdigit():
|
||||
_contract_fids = [
|
||||
f.id for f in Facility.query.filter_by(project_id=int(contract_filter)).all()
|
||||
]
|
||||
q = q.filter(Inspection.facility_id.in_(_contract_fids)) if _contract_fids else q.filter(False)
|
||||
if facility_filter.isdigit():
|
||||
q = q.filter(Inspection.facility_id == int(facility_filter))
|
||||
if date_from_filter:
|
||||
try:
|
||||
q = q.filter(Inspection.inspection_date >= datetime.strptime(date_from_filter, '%Y-%m-%d'))
|
||||
except ValueError:
|
||||
pass
|
||||
if date_to_filter:
|
||||
try:
|
||||
_dt = datetime.strptime(date_to_filter, '%Y-%m-%d').replace(hour=23, minute=59, second=59)
|
||||
q = q.filter(Inspection.inspection_date <= _dt)
|
||||
except ValueError:
|
||||
pass
|
||||
if score_min_filter:
|
||||
try:
|
||||
q = q.filter(Inspection.overall_score >= float(score_min_filter))
|
||||
except ValueError:
|
||||
pass
|
||||
if score_max_filter:
|
||||
try:
|
||||
q = q.filter(Inspection.overall_score <= float(score_max_filter))
|
||||
except ValueError:
|
||||
pass
|
||||
if inspector_filter.isdigit() and current_user.role != 'inspector':
|
||||
q = q.filter(Inspection.inspector_id == int(inspector_filter))
|
||||
|
||||
inspections = q.all()
|
||||
|
||||
# Build a human-readable filter summary for the PDF header
|
||||
filter_parts = []
|
||||
if status_filter:
|
||||
label = {'follow_up': 'Flagged Follow-up', 'has_issues': 'Has Issues'}.get(
|
||||
status_filter, status_filter.replace('_', ' ').title()
|
||||
)
|
||||
filter_parts.append(f'Status: {label}')
|
||||
if contract_filter.isdigit():
|
||||
p = db.session.get(Project, int(contract_filter))
|
||||
if p:
|
||||
filter_parts.append(f'Contract: {p.name}')
|
||||
if facility_filter.isdigit():
|
||||
f = db.session.get(Facility, int(facility_filter))
|
||||
if f:
|
||||
filter_parts.append(f'Facility: {f.name}')
|
||||
if date_from_filter:
|
||||
filter_parts.append(f'From: {date_from_filter}')
|
||||
if date_to_filter:
|
||||
filter_parts.append(f'To: {date_to_filter}')
|
||||
if score_min_filter:
|
||||
filter_parts.append(f'Min score: {score_min_filter}%')
|
||||
if score_max_filter:
|
||||
filter_parts.append(f'Max score: {score_max_filter}%')
|
||||
if inspector_filter.isdigit() and current_user.role != 'inspector':
|
||||
u = db.session.get(User, int(inspector_filter))
|
||||
if u:
|
||||
filter_parts.append(f'Inspector: {u.display_name}')
|
||||
|
||||
filter_summary = ' | '.join(filter_parts) if filter_parts else 'All inspections'
|
||||
|
||||
pdf_bytes = generate_inspections_list_pdf(inspections, filter_summary)
|
||||
filename = f'inspections_list_{now_eastern().strftime("%Y%m%d_%H%M")}.pdf'
|
||||
|
||||
log_action(ACTION_EXPORT, 'Inspection', None, 'Inspections List',
|
||||
f'format=pdf; filters={filter_summary}; count={len(inspections)}')
|
||||
|
||||
return Response(
|
||||
pdf_bytes,
|
||||
mimetype='application/pdf',
|
||||
headers={'Content-Disposition': f'attachment; filename="{filename}"'},
|
||||
)
|
||||
|
||||
|
||||
# ── Export to PDF ─────────────────────────────────────────────────────────────
|
||||
|
||||
@bp.route('/<int:inspection_id>/export-pdf')
|
||||
|
||||
Reference in New Issue
Block a user