04/27 Fixed some issues
This commit is contained in:
+5
-5
@@ -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)
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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))
|
||||||
@@ -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]
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user