04/27 Fixed some issues

This commit is contained in:
2026-04-27 13:36:30 -04:00
parent ff0650e1d9
commit 821cd929f1
7 changed files with 75 additions and 10 deletions
+5 -5
View File
@@ -5,7 +5,8 @@ from app.utils.time_utils import now_eastern
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id): def load_user(user_id):
return User.query.get(int(user_id)) from app import db
return db.session.get(User, int(user_id))
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
__tablename__ = 'users' __tablename__ = 'users'
@@ -16,10 +17,9 @@ class User(UserMixin, db.Model):
email = db.Column(db.String(255), unique=True, nullable=False, index=True) email = db.Column(db.String(255), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(255), nullable=False) password_hash = db.Column(db.String(255), nullable=False)
role = db.Column( role = db.Column(
# 'supervisor' retained temporarily so the Enum is valid before the # Phase 11 migration complete — 'supervisor' removed from both the DB
# migration UPDATE runs. The migration removes it after all rows are # ENUM and this Python-side declaration. Director is the canonical role.
# updated to 'director'. db.Enum('admin', 'director', 'inspector', 'project_manager', 'customer'),
db.Enum('admin', 'supervisor', 'director', 'inspector', 'project_manager', 'customer'),
nullable=False nullable=False
) )
created_at = db.Column(db.DateTime, default=now_eastern) created_at = db.Column(db.DateTime, default=now_eastern)
+3
View File
@@ -1,3 +1,4 @@
import logging
from flask import Blueprint, render_template from flask import Blueprint, render_template
from flask_login import login_required, current_user from flask_login import login_required, current_user
from app import db from app import db
@@ -13,6 +14,8 @@ from app.utils.time_utils import now_eastern
bp = Blueprint('dashboard', __name__) bp = Blueprint('dashboard', __name__)
logger = logging.getLogger(__name__)
@bp.route('/') @bp.route('/')
@bp.route('/dashboard') @bp.route('/dashboard')
+15
View File
@@ -1,3 +1,4 @@
import logging
from flask import Blueprint, render_template, redirect, url_for, flash, request, abort from flask import Blueprint, render_template, redirect, url_for, flash, request, abort
from flask_login import login_required, current_user from flask_login import login_required, current_user
from app import db from app import db
@@ -10,6 +11,8 @@ from app.utils.scope import get_customer_scope
bp = Blueprint('facilities', __name__, url_prefix='/facilities') bp = Blueprint('facilities', __name__, url_prefix='/facilities')
logger = logging.getLogger(__name__)
@bp.route('/') @bp.route('/')
@login_required @login_required
def list_facilities(): def list_facilities():
@@ -43,6 +46,8 @@ def create_facility():
db.session.add(facility) db.session.add(facility)
db.session.commit() db.session.commit()
logger.info('FACILITIES | create | user=%s | facility_id=%s name=%r',
current_user.username, facility.id, facility.name)
log_action(ACTION_CREATE, 'Facility', facility.id, facility.name, log_action(ACTION_CREATE, 'Facility', facility.id, facility.name,
f'contact={facility.contact_person or ""}; project_id={facility.project_id}; active={facility.active}') f'contact={facility.contact_person or ""}; project_id={facility.project_id}; active={facility.active}')
flash(f'Facility "{facility.name}" created successfully.', 'success') flash(f'Facility "{facility.name}" created successfully.', 'success')
@@ -85,6 +90,8 @@ def edit_facility(facility_id):
facility.active = form.active.data facility.active = form.active.data
db.session.commit() db.session.commit()
logger.info('FACILITIES | edit | user=%s | facility_id=%s name=%r',
current_user.username, facility.id, facility.name)
log_action(ACTION_UPDATE, 'Facility', facility.id, facility.name, log_action(ACTION_UPDATE, 'Facility', facility.id, facility.name,
f'project_id={facility.project_id}; active={facility.active}') f'project_id={facility.project_id}; active={facility.active}')
flash(f'Facility "{facility.name}" updated successfully.', 'success') flash(f'Facility "{facility.name}" updated successfully.', 'success')
@@ -108,6 +115,8 @@ def delete_facility(facility_id):
facility_id_snap = facility.id facility_id_snap = facility.id
db.session.delete(facility) db.session.delete(facility)
db.session.commit() db.session.commit()
logger.info('FACILITIES | delete | user=%s | facility_id=%s name=%r',
current_user.username, facility_id_snap, facility_name)
log_action(ACTION_DELETE, 'Facility', facility_id_snap, facility_name) log_action(ACTION_DELETE, 'Facility', facility_id_snap, facility_name)
flash(f'Facility "{facility_name}" has been permanently deleted.', 'success') flash(f'Facility "{facility_name}" has been permanently deleted.', 'success')
return redirect(url_for('facilities.list_facilities')) return redirect(url_for('facilities.list_facilities'))
@@ -134,6 +143,8 @@ def create_area(facility_id):
db.session.add(area) db.session.add(area)
db.session.commit() db.session.commit()
logger.info('FACILITIES | create_area | user=%s | area_id=%s name=%r facility=%r',
current_user.username, area.id, area.name, facility.name)
log_action(ACTION_CREATE, 'Area', area.id, area.name, log_action(ACTION_CREATE, 'Area', area.id, area.name,
f'facility={facility.name}; type={area.area_type or ""}') f'facility={facility.name}; type={area.area_type or ""}')
flash(f'Area "{area.name}" created successfully.', 'success') flash(f'Area "{area.name}" created successfully.', 'success')
@@ -159,6 +170,8 @@ def edit_area(area_id):
area.facility_id = form.facility_id.data area.facility_id = form.facility_id.data
db.session.commit() db.session.commit()
logger.info('FACILITIES | edit_area | user=%s | area_id=%s name=%r',
current_user.username, area.id, area.name)
log_action(ACTION_UPDATE, 'Area', area.id, area.name, log_action(ACTION_UPDATE, 'Area', area.id, area.name,
f'facility_id={area.facility_id}; type={area.area_type or ""}') f'facility_id={area.facility_id}; type={area.area_type or ""}')
flash(f'Area "{area.name}" updated successfully.', 'success') flash(f'Area "{area.name}" updated successfully.', 'success')
@@ -192,6 +205,8 @@ def delete_area(area_id):
area_id_snap = area.id area_id_snap = area.id
db.session.delete(area) db.session.delete(area)
db.session.commit() db.session.commit()
logger.info('FACILITIES | delete_area | user=%s | area_id=%s name=%r',
current_user.username, area_id_snap, area_name)
log_action(ACTION_DELETE, 'Area', area_id_snap, area_name) log_action(ACTION_DELETE, 'Area', area_id_snap, area_name)
flash(f'Area "{area_name}" deleted successfully.', 'success') flash(f'Area "{area_name}" deleted successfully.', 'success')
return redirect(url_for('facilities.view_facility', facility_id=facility_id)) return redirect(url_for('facilities.view_facility', facility_id=facility_id))
+2 -2
View File
@@ -142,7 +142,7 @@ def view(issue_id):
return redirect(url_for('issues.index')) return redirect(url_for('issues.index'))
form = IssueUpdateForm(obj=issue) form = IssueUpdateForm(obj=issue)
staff = User.query.filter(User.role.in_(['director','inspector'])).order_by(User.username).all() staff = User.query.filter(User.role.in_(['admin', 'director', 'inspector'])).order_by(User.username).all()
form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.username) for u in staff] form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.username) for u in staff]
form.status.data = form.status.data or issue.status form.status.data = form.status.data or issue.status
@@ -385,7 +385,7 @@ def unfollow(issue_id):
def create(): def create():
form = IssueForm() form = IssueForm()
areas = Area.query.join(Facility).filter(Facility.active == True).order_by(Facility.name, Area.name).all() areas = Area.query.join(Facility).filter(Facility.active == True).order_by(Facility.name, Area.name).all()
staff = User.query.filter(User.role.in_(['director','inspector'])).order_by(User.username).all() staff = User.query.filter(User.role.in_(['admin', 'director', 'inspector'])).order_by(User.username).all()
form.area_id.choices = [(a.id, f"{a.facility.name}{a.name}") for a in areas] form.area_id.choices = [(a.id, f"{a.facility.name}{a.name}") for a in areas]
form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.username) for u in staff] form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.username) for u in staff]
+26
View File
@@ -1,5 +1,6 @@
import csv import csv
import io import io
import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from app.utils.time_utils import now_eastern from app.utils.time_utils import now_eastern
from flask import (Blueprint, render_template, request, from flask import (Blueprint, render_template, request,
@@ -13,9 +14,12 @@ from app.models.issue import Issue
from app.models.user import User from app.models.user import User
from app.utils.decorators import supervisor_required from app.utils.decorators import supervisor_required
from app.utils.scope import get_customer_scope from app.utils.scope import get_customer_scope
from app.utils.audit import log_action, ACTION_EXPORT
bp = Blueprint('reports', __name__, url_prefix='/reports') bp = Blueprint('reports', __name__, url_prefix='/reports')
logger = logging.getLogger(__name__)
def _date_range(): def _date_range():
"""Parse ?start= and ?end= query params; default to last 30 days.""" """Parse ?start= and ?end= query params; default to last 30 days."""
@@ -360,6 +364,17 @@ def facility_scorecard(facility_id):
def export_inspections(): def export_inspections():
start, end = _date_range() start, end = _date_range()
logger.info(
'REPORTS | export_inspections | user=%s | range=%s to %s',
current_user.username,
start.strftime('%Y-%m-%d'),
end.strftime('%Y-%m-%d'),
)
log_action(
ACTION_EXPORT, 'Inspection', None, 'CSV Export',
f'range={start.strftime("%Y-%m-%d")} to {end.strftime("%Y-%m-%d")}',
)
rows = db.session.query( rows = db.session.query(
Inspection.id, Inspection.id,
Inspection.inspection_date, Inspection.inspection_date,
@@ -414,6 +429,17 @@ def export_inspections():
def export_issues(): def export_issues():
start, end = _date_range() start, end = _date_range()
logger.info(
'REPORTS | export_issues | user=%s | range=%s to %s',
current_user.username,
start.strftime('%Y-%m-%d'),
end.strftime('%Y-%m-%d'),
)
log_action(
ACTION_EXPORT, 'Issue', None, 'CSV Export',
f'range={start.strftime("%Y-%m-%d")} to {end.strftime("%Y-%m-%d")}',
)
rows = db.session.query( rows = db.session.query(
Issue.id, Issue.id,
Issue.reported_at, Issue.reported_at,
+21
View File
@@ -1,3 +1,4 @@
import logging
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, abort from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, abort
from flask_login import login_required, current_user from flask_login import login_required, current_user
from app import db from app import db
@@ -9,6 +10,8 @@ from app.utils.audit import log_action, ACTION_CREATE, ACTION_UPDATE, ACTION_DEL
bp = Blueprint('templates', __name__, url_prefix='/templates') bp = Blueprint('templates', __name__, url_prefix='/templates')
logger = logging.getLogger(__name__)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Template CRUD # Template CRUD
@@ -36,6 +39,8 @@ def create_template():
) )
db.session.add(template) db.session.add(template)
db.session.commit() db.session.commit()
logger.info('TEMPLATES | create | user=%s | template_id=%s name=%r',
current_user.username, template.id, template.name)
log_action(ACTION_CREATE, 'Template', template.id, template.name, log_action(ACTION_CREATE, 'Template', template.id, template.name,
f'frequency={template.frequency}') f'frequency={template.frequency}')
@@ -73,6 +78,8 @@ def edit_template(template_id):
template.description = form.description.data template.description = form.description.data
template.frequency = form.frequency.data template.frequency = form.frequency.data
db.session.commit() db.session.commit()
logger.info('TEMPLATES | edit | user=%s | template_id=%s name=%r',
current_user.username, template.id, template.name)
log_action(ACTION_UPDATE, 'Template', template.id, template.name, log_action(ACTION_UPDATE, 'Template', template.id, template.name,
f'frequency={template.frequency}') f'frequency={template.frequency}')
flash(f'Template "{template.name}" updated successfully.', 'success') flash(f'Template "{template.name}" updated successfully.', 'success')
@@ -114,6 +121,8 @@ def rename_template(template_id):
template.frequency = new_frequency template.frequency = new_frequency
db.session.commit() db.session.commit()
logger.info('TEMPLATES | rename | user=%s | template_id=%s name=%r',
current_user.username, template.id, template.name)
log_action(ACTION_UPDATE, 'Template', template.id, template.name, log_action(ACTION_UPDATE, 'Template', template.id, template.name,
f'frequency={template.frequency}; via=rename') f'frequency={template.frequency}; via=rename')
flash(f'Template "{template.name}" updated successfully.', 'success') flash(f'Template "{template.name}" updated successfully.', 'success')
@@ -136,6 +145,8 @@ def delete_template(template_id):
template_id_snap = template.id template_id_snap = template.id
db.session.delete(template) db.session.delete(template)
db.session.commit() db.session.commit()
logger.info('TEMPLATES | delete | user=%s | template_id=%s name=%r',
current_user.username, template_id_snap, template_name)
log_action(ACTION_DELETE, 'Template', template_id_snap, template_name) log_action(ACTION_DELETE, 'Template', template_id_snap, template_name)
flash(f'Template "{template_name}" deleted successfully.', 'success') flash(f'Template "{template_name}" deleted successfully.', 'success')
@@ -178,6 +189,8 @@ def duplicate_template(template_id):
new_tpl.form_schema = src.form_schema new_tpl.form_schema = src.form_schema
db.session.commit() db.session.commit()
logger.info('TEMPLATES | duplicate | user=%s | new_template_id=%s source_id=%s',
current_user.username, new_tpl.id, src.id)
log_action(ACTION_CREATE, 'Template', new_tpl.id, new_tpl.name, log_action(ACTION_CREATE, 'Template', new_tpl.id, new_tpl.name,
f'duplicated_from={src.id}; frequency={new_tpl.frequency}') f'duplicated_from={src.id}; frequency={new_tpl.frequency}')
@@ -274,6 +287,8 @@ def save_form_schema(template_id):
template.form_schema = sanitised template.form_schema = sanitised
db.session.commit() db.session.commit()
logger.info('TEMPLATES | save_form_schema | user=%s | template_id=%s fields=%s',
current_user.username, template.id, len(sanitised))
log_action(ACTION_UPDATE, 'Template', template.id, template.name, log_action(ACTION_UPDATE, 'Template', template.id, template.name,
f'form_schema saved; field_count={len(sanitised)}') f'form_schema saved; field_count={len(sanitised)}')
@@ -323,6 +338,8 @@ def create_checklist_item(template_id):
) )
db.session.add(item) db.session.add(item)
db.session.commit() db.session.commit()
logger.info('TEMPLATES | create_checklist_item | user=%s | item_id=%s template_id=%s',
current_user.username, item.id, template.id)
log_action(ACTION_CREATE, 'ChecklistItem', item.id, item.item_description[:80], log_action(ACTION_CREATE, 'ChecklistItem', item.id, item.item_description[:80],
f'template_id={template.id}; category={item.category or ""}; ' f'template_id={template.id}; category={item.category or ""}; '
f'scoring_type={item.scoring_type}') f'scoring_type={item.scoring_type}')
@@ -354,6 +371,8 @@ def edit_checklist_item(item_id):
item.weight = form.weight.data item.weight = form.weight.data
item.requires_photo = form.requires_photo.data item.requires_photo = form.requires_photo.data
db.session.commit() db.session.commit()
logger.info('TEMPLATES | edit_checklist_item | user=%s | item_id=%s template_id=%s',
current_user.username, item.id, item.template_id)
log_action(ACTION_UPDATE, 'ChecklistItem', item.id, item.item_description[:80], log_action(ACTION_UPDATE, 'ChecklistItem', item.id, item.item_description[:80],
f'template_id={item.template_id}; category={item.category or ""}; ' f'template_id={item.template_id}; category={item.category or ""}; '
f'scoring_type={item.scoring_type}') f'scoring_type={item.scoring_type}')
@@ -381,6 +400,8 @@ def delete_checklist_item(item_id):
item_id_snap = item.id item_id_snap = item.id
db.session.delete(item) db.session.delete(item)
db.session.commit() db.session.commit()
logger.info('TEMPLATES | delete_checklist_item | user=%s | item_id=%s template_id=%s',
current_user.username, item_id_snap, template_id)
log_action(ACTION_DELETE, 'ChecklistItem', item_id_snap, item_desc, log_action(ACTION_DELETE, 'ChecklistItem', item_id_snap, item_desc,
f'template_id={template_id}') f'template_id={template_id}')
flash('Checklist item deleted successfully.', 'success') flash('Checklist item deleted successfully.', 'success')
+3 -3
View File
@@ -310,7 +310,7 @@ def notify_customers_for_facility(
from app.models.facility import Facility from app.models.facility import Facility
from app.models.user import User from app.models.user import User
facility = Facility.query.get(facility_id) facility = db.session.get(Facility, facility_id)
if not facility: if not facility:
logger.warning( logger.warning(
'notify_customers_for_facility | facility_id=%s not found', facility_id 'notify_customers_for_facility | facility_id=%s not found', facility_id
@@ -342,7 +342,7 @@ def notify_customers_for_facility(
return return
for user_id in notified_user_ids: for user_id in notified_user_ids:
user = User.query.get(user_id) user = db.session.get(User, user_id)
if not user or not user.active or user.role != 'customer': if not user or not user.active or user.role != 'customer':
continue continue
try: try:
@@ -397,7 +397,7 @@ def send_pending_digests(frequency: str = 'daily'):
sent_count = 0 sent_count = 0
for user_id in pending_user_ids: for user_id in pending_user_ids:
user = User.query.get(user_id) user = db.session.get(User, user_id)
if not user or not user.email: if not user or not user.email:
continue continue