Jul 9 - Update to allow customer to manage their facilities' QR codes

This commit is contained in:
2026-07-09 09:58:43 -04:00
parent 97a559d5c0
commit b2ac816bc2
6 changed files with 58 additions and 28 deletions
+36 -22
View File
@@ -97,7 +97,9 @@ def view_facility(facility_id):
return render_template('facilities/view.html', facility=facility, areas=areas)
# ── Public QR code (staff-only generation) ────────────────────────────────────
# ── Public QR code ────────────────────────────────────────────────────────────
# Staff (admin/director/pm/inspector) may access QR for any facility; customers
# may access QR only for facilities in their assigned scope.
def _public_facility_url(facility):
"""Absolute URL the QR encodes — the login-free occupant summary page."""
@@ -108,15 +110,27 @@ def _public_facility_url(facility):
token=facility.public_token, _external=True)
@bp.route('/<int:facility_id>/qr.png')
@login_required
def facility_qr_png(facility_id):
"""Return the facility's QR code as a PNG image (staff only)."""
if current_user.role == 'customer':
abort(403)
def _facility_for_qr_or_403(facility_id):
"""Load a facility for a QR action, enforcing customer facility scope.
Customers may only touch QR codes for facilities they are assigned to; all
other (staff) roles have unrestricted QR access.
"""
facility = db.session.get(Facility, facility_id)
if facility is None:
abort(404)
if current_user.role == 'customer':
cids = get_customer_scope(current_user) or []
if facility.id not in cids:
abort(403)
return facility
@bp.route('/<int:facility_id>/qr.png')
@login_required
def facility_qr_png(facility_id):
"""Return the facility's QR code as a PNG image."""
facility = _facility_for_qr_or_403(facility_id)
# Token may not exist for pre-phase34 rows viewed before any commit.
created = not facility.public_token
@@ -141,11 +155,7 @@ def facility_qr_png(facility_id):
@login_required
def facility_qr_page(facility_id):
"""Printable page: facility name + QR + public URL + posting instructions."""
if current_user.role == 'customer':
abort(403)
facility = db.session.get(Facility, facility_id)
if facility is None:
abort(404)
facility = _facility_for_qr_or_403(facility_id)
public_url = _public_facility_url(facility)
db.session.commit() # persist token if it was just generated
return render_template('facilities/qr.html',
@@ -154,12 +164,15 @@ def facility_qr_page(facility_id):
@bp.route('/<int:facility_id>/qr/regenerate', methods=['POST'])
@login_required
@supervisor_required
def facility_qr_regenerate(facility_id):
"""Mint a NEW public token, invalidating any previously printed QR code."""
facility = db.session.get(Facility, facility_id)
if facility is None:
abort(404)
"""Mint a NEW public token, invalidating any previously printed QR code.
Allowed for admin/director, and for customers on their own assigned
facilities. Project managers and inspectors cannot regenerate.
"""
facility = _facility_for_qr_or_403(facility_id)
if current_user.role not in ('admin', 'director', 'customer'):
abort(403)
facility.public_token = Facility.generate_public_token()
db.session.commit()
@@ -178,17 +191,18 @@ def facility_qr_regenerate(facility_id):
def facility_qr_print_all():
"""Printable sheet of QR codes for all facilities the user can see.
Optional ?contract_id=<id> limits the sheet to one contract. Customers have
no QR access (403); inspectors are scoped to their contracted facilities.
Optional ?contract_id=<id> limits the sheet to one contract. Inspectors are
scoped to their contracted facilities; customers to their assigned
facilities; managers see all active facilities.
"""
if current_user.role == 'customer':
abort(403)
contract_id = request.args.get('contract_id', type=int)
if current_user.role == 'inspector':
fids = get_inspector_scope(current_user) or []
query = Facility.query.filter(Facility.id.in_(fids), Facility.active == True)
elif current_user.role == 'customer':
fids = get_customer_scope(current_user) or []
query = Facility.query.filter(Facility.id.in_(fids), Facility.active == True)
else:
query = Facility.query.filter(Facility.active == True)
+1 -3
View File
@@ -8,12 +8,10 @@
<h2><i class="bi bi-building"></i> Facilities</h2>
</div>
<div class="col-md-6 text-end">
{% if current_user.role != 'customer' %}
<a href="{{ url_for('facilities.facility_qr_print_all') }}"
class="btn btn-outline-dark" title="Printable sheet of every facility's QR code">
class="btn btn-outline-dark" title="Printable sheet of your facilities' QR codes">
<i class="bi bi-qr-code"></i> Print All QR Codes
</a>
{% endif %}
{% if current_user.role in ['admin', 'director'] %}
<a href="{{ url_for('facilities.create_facility') }}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Add Facility
+1 -1
View File
@@ -17,7 +17,7 @@
<i class="bi bi-arrow-left"></i> Back to Facility
</a>
<div class="d-flex gap-2">
{% if current_user.role in ['admin', 'director'] %}
{% if current_user.role in ['admin', 'director', 'customer'] %}
<form method="POST"
action="{{ url_for('facilities.facility_qr_regenerate', facility_id=facility.id) }}"
onsubmit="return confirm('Regenerate this QR code? Any codes already printed and posted for {{ facility.name }} will STOP working and must be reprinted.');">
+1 -1
View File
@@ -17,7 +17,7 @@
<i class="bi bi-graph-up-arrow"></i> Scorecard
</a>
{% endif %}
{% if current_user.role in ['admin', 'director', 'project_manager'] %}
{% if current_user.role in ['admin', 'director', 'project_manager', 'customer'] %}
<a href="{{ url_for('facilities.facility_qr_page', facility_id=facility.id) }}"
class="btn btn-outline-dark" title="Printable QR code for this facility">
<i class="bi bi-qr-code"></i> QR Code