Phase 2: initiate

This commit is contained in:
2026-02-06 16:38:19 -05:00
parent 7a4159b013
commit 66b3bb7642
21 changed files with 1502 additions and 62 deletions
+83 -12
View File
@@ -1,30 +1,101 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required
from flask_login import login_user, logout_user, login_required, current_user
from app import db
from app.models.user import User
from app.utils.forms import LoginForm, UserForm
from app.utils.decorators import admin_required
bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
if current_user.is_authenticated:
return redirect(url_for('dashboard.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user)
next_page = request.args.get('next')
flash(f'Welcome back, {user.username}!', 'success')
return redirect(next_page or url_for('dashboard.index'))
else:
flash('Invalid username or password', 'danger')
return render_template('login.html')
flash('Invalid credentials. Please try again.', 'danger')
return render_template('auth/login.html', form=form)
@bp.route('/logout')
@login_required
def logout():
logout_user()
flash('You have been logged out successfully', 'success')
flash('Successfully logged out.', 'success')
return redirect(url_for('auth.login'))
@bp.route('/users')
@login_required
@admin_required
def list_users():
users = User.query.order_by(User.created_at.desc()).all()
return render_template('auth/users.html', users=users)
@bp.route('/users/new', methods=['GET', 'POST'])
@login_required
@admin_required
def create_user():
form = UserForm()
if form.validate_on_submit():
user = User(
username=form.username.data,
email=form.email.data,
role=form.role.data
)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash(f'User {user.username} created successfully.', 'success')
return redirect(url_for('auth.list_users'))
return render_template('auth/user_form.html', form=form, title='Create User')
@bp.route('/users/<int:user_id>/edit', methods=['GET', 'POST'])
@login_required
@admin_required
def edit_user(user_id):
user = User.query.get_or_404(user_id)
form = UserForm(user=user, obj=user)
if form.validate_on_submit():
user.username = form.username.data
user.email = form.email.data
user.role = form.role.data
if form.password.data:
user.set_password(form.password.data)
db.session.commit()
flash(f'User {user.username} updated successfully.', 'success')
return redirect(url_for('auth.list_users'))
return render_template('auth/user_form.html', form=form, user=user, title='Edit User')
@bp.route('/users/<int:user_id>/delete', methods=['POST'])
@login_required
@admin_required
def delete_user(user_id):
user = User.query.get_or_404(user_id)
if user.id == current_user.id:
flash('Cannot delete your own account.', 'danger')
return redirect(url_for('auth.list_users'))
username = user.username
db.session.delete(user)
db.session.commit()
flash(f'User {username} deleted successfully.', 'success')
return redirect(url_for('auth.list_users'))
+83 -1
View File
@@ -1,5 +1,11 @@
from flask import Blueprint, render_template
from flask_login import login_required, current_user
from app.models.inspection import Inspection, InspectionTemplate
from app.models.facility import Facility
from app.models.issue import Issue
from app.models.user import User
from sqlalchemy import func
from datetime import datetime, timedelta
bp = Blueprint('dashboard', __name__)
@@ -7,4 +13,80 @@ bp = Blueprint('dashboard', __name__)
@bp.route('/dashboard')
@login_required
def index():
return render_template('dashboard.html', user=current_user)
# Get today's date range
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
today_end = today_start + timedelta(days=1)
# Statistics for today
if current_user.role == 'inspector':
today_inspections = Inspection.query.filter(
Inspection.inspector_id == current_user.id,
Inspection.inspection_date >= today_start,
Inspection.inspection_date < today_end
).count()
completed_today = Inspection.query.filter(
Inspection.inspector_id == current_user.id,
Inspection.status == 'completed',
Inspection.inspection_date >= today_start,
Inspection.inspection_date < today_end
).count()
open_issues = Issue.query.join(Inspection).filter(
Inspection.inspector_id == current_user.id,
Issue.status.in_(['open', 'in_progress'])
).count()
else:
today_inspections = Inspection.query.filter(
Inspection.inspection_date >= today_start,
Inspection.inspection_date < today_end
).count()
completed_today = Inspection.query.filter(
Inspection.status == 'completed',
Inspection.inspection_date >= today_start,
Inspection.inspection_date < today_end
).count()
open_issues = Issue.query.filter(
Issue.status.in_(['open', 'in_progress'])
).count()
# Calculate average score (last 30 days)
thirty_days_ago = datetime.utcnow() - timedelta(days=30)
avg_score_query = Inspection.query.filter(
Inspection.status == 'completed',
Inspection.overall_score.isnot(None),
Inspection.inspection_date >= thirty_days_ago
)
if current_user.role == 'inspector':
avg_score_query = avg_score_query.filter(Inspection.inspector_id == current_user.id)
avg_score = db.session.query(func.avg(Inspection.overall_score)).filter(
Inspection.id.in_([i.id for i in avg_score_query.all()])
).scalar()
# Recent inspections
recent_inspections_query = Inspection.query.order_by(Inspection.inspection_date.desc()).limit(5)
if current_user.role == 'inspector':
recent_inspections_query = recent_inspections_query.filter(Inspection.inspector_id == current_user.id)
recent_inspections = recent_inspections_query.all()
# System statistics (admin/supervisor only)
total_facilities = Facility.query.filter_by(active=True).count() if current_user.role in ['admin', 'supervisor'] else 0
total_templates = InspectionTemplate.query.count() if current_user.role in ['admin', 'supervisor'] else 0
total_users = User.query.count() if current_user.role == 'admin' else 0
return render_template('dashboard.html',
today_inspections=today_inspections,
completed_today=completed_today,
open_issues=open_issues,
avg_score=round(avg_score, 2) if avg_score else None,
recent_inspections=recent_inspections,
total_facilities=total_facilities,
total_templates=total_templates,
total_users=total_users
)
+149
View File
@@ -0,0 +1,149 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required
from app import db
from app.models.facility import Facility, Area
from app.utils.forms import FacilityForm, AreaForm
from app.utils.decorators import supervisor_required
bp = Blueprint('facilities', __name__, url_prefix='/facilities')
@bp.route('/')
@login_required
def list_facilities():
facilities = Facility.query.order_by(Facility.name).all()
return render_template('facilities/list.html', facilities=facilities)
@bp.route('/new', methods=['GET', 'POST'])
@login_required
@supervisor_required
def create_facility():
form = FacilityForm()
if form.validate_on_submit():
facility = Facility(
name=form.name.data,
address=form.address.data,
contact_person=form.contact_person.data,
contact_phone=form.contact_phone.data,
active=form.active.data
)
db.session.add(facility)
db.session.commit()
flash(f'Facility "{facility.name}" created successfully.', 'success')
return redirect(url_for('facilities.view_facility', facility_id=facility.id))
return render_template('facilities/form.html', form=form, title='Create Facility')
@bp.route('/<int:facility_id>')
@login_required
def view_facility(facility_id):
facility = Facility.query.get_or_404(facility_id)
areas = facility.areas.order_by(Area.name).all()
return render_template('facilities/view.html', facility=facility, areas=areas)
@bp.route('/<int:facility_id>/edit', methods=['GET', 'POST'])
@login_required
@supervisor_required
def edit_facility(facility_id):
facility = Facility.query.get_or_404(facility_id)
form = FacilityForm(obj=facility)
if form.validate_on_submit():
facility.name = form.name.data
facility.address = form.address.data
facility.contact_person = form.contact_person.data
facility.contact_phone = form.contact_phone.data
facility.active = form.active.data
db.session.commit()
flash(f'Facility "{facility.name}" updated successfully.', 'success')
return redirect(url_for('facilities.view_facility', facility_id=facility.id))
return render_template('facilities/form.html', form=form, facility=facility, title='Edit Facility')
@bp.route('/<int:facility_id>/delete', methods=['POST'])
@login_required
@supervisor_required
def delete_facility(facility_id):
facility = Facility.query.get_or_404(facility_id)
# Check if facility has inspections
if facility.inspections.count() > 0:
flash('Cannot delete facility with existing inspections.', 'danger')
return redirect(url_for('facilities.view_facility', facility_id=facility.id))
facility_name = facility.name
db.session.delete(facility)
db.session.commit()
flash(f'Facility "{facility_name}" deleted successfully.', 'success')
return redirect(url_for('facilities.list_facilities'))
# Area Management Routes
@bp.route('/<int:facility_id>/areas/new', methods=['GET', 'POST'])
@login_required
@supervisor_required
def create_area(facility_id):
facility = Facility.query.get_or_404(facility_id)
form = AreaForm()
form.facility_id.choices = [(facility.id, facility.name)]
form.facility_id.data = facility.id
if form.validate_on_submit():
area = Area(
name=form.name.data,
area_type=form.area_type.data,
facility_id=facility.id
)
db.session.add(area)
db.session.commit()
flash(f'Area "{area.name}" created successfully.', 'success')
return redirect(url_for('facilities.view_facility', facility_id=facility.id))
return render_template('facilities/area_form.html', form=form, facility=facility, title='Create Area')
@bp.route('/areas/<int:area_id>/edit', methods=['GET', 'POST'])
@login_required
@supervisor_required
def edit_area(area_id):
area = Area.query.get_or_404(area_id)
form = AreaForm(obj=area)
# Populate facility choices
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
form.facility_id.choices = [(f.id, f.name) for f in facilities]
if form.validate_on_submit():
area.name = form.name.data
area.area_type = form.area_type.data
area.facility_id = form.facility_id.data
db.session.commit()
flash(f'Area "{area.name}" updated successfully.', 'success')
return redirect(url_for('facilities.view_facility', facility_id=area.facility_id))
return render_template('facilities/area_form.html', form=form, area=area, facility=area.facility, title='Edit Area')
@bp.route('/areas/<int:area_id>/delete', methods=['POST'])
@login_required
@supervisor_required
def delete_area(area_id):
area = Area.query.get_or_404(area_id)
facility_id = area.facility_id
# Check if area has inspections
if area.inspections.count() > 0:
flash('Cannot delete area with existing inspections.', 'danger')
return redirect(url_for('facilities.view_facility', facility_id=facility_id))
area_name = area.name
db.session.delete(area)
db.session.commit()
flash(f'Area "{area_name}" deleted successfully.', 'success')
return redirect(url_for('facilities.view_facility', facility_id=facility_id))
+165 -2
View File
@@ -1,7 +1,170 @@
from flask import Blueprint
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
from flask_login import login_required, current_user
from app import db
from app.models.inspection import InspectionTemplate, ChecklistItem
from app.utils.forms import InspectionTemplateForm, ChecklistItemForm
from app.utils.decorators import supervisor_required
bp = Blueprint('templates', __name__, url_prefix='/templates')
@bp.route('/')
@login_required
def index():
return "Templates module - Coming in Phase 2"
templates = InspectionTemplate.query.order_by(InspectionTemplate.name).all()
return render_template('templates/list.html', templates=templates)
@bp.route('/new', methods=['GET', 'POST'])
@login_required
@supervisor_required
def create_template():
form = InspectionTemplateForm()
if form.validate_on_submit():
template = InspectionTemplate(
name=form.name.data,
description=form.description.data,
frequency=form.frequency.data,
created_by=current_user.id
)
db.session.add(template)
db.session.commit()
flash(f'Template "{template.name}" created successfully.', 'success')
return redirect(url_for('templates.edit_template', template_id=template.id))
return render_template('templates/form.html', form=form, title='Create Inspection Template')
@bp.route('/<int:template_id>')
@login_required
def view_template(template_id):
template = InspectionTemplate.query.get_or_404(template_id)
checklist_items = template.checklist_items.order_by(ChecklistItem.display_order, ChecklistItem.category).all()
# Group items by category
items_by_category = {}
for item in checklist_items:
category = item.category or 'General'
if category not in items_by_category:
items_by_category[category] = []
items_by_category[category].append(item)
return render_template('templates/view.html', template=template, items_by_category=items_by_category)
@bp.route('/<int:template_id>/edit', methods=['GET', 'POST'])
@login_required
@supervisor_required
def edit_template(template_id):
template = InspectionTemplate.query.get_or_404(template_id)
form = InspectionTemplateForm(obj=template)
if form.validate_on_submit():
template.name = form.name.data
template.description = form.description.data
template.frequency = form.frequency.data
db.session.commit()
flash(f'Template "{template.name}" updated successfully.', 'success')
return redirect(url_for('templates.view_template', template_id=template.id))
checklist_items = template.checklist_items.order_by(ChecklistItem.display_order, ChecklistItem.category).all()
return render_template('templates/edit.html', form=form, template=template, checklist_items=checklist_items)
@bp.route('/<int:template_id>/delete', methods=['POST'])
@login_required
@supervisor_required
def delete_template(template_id):
template = InspectionTemplate.query.get_or_404(template_id)
# Check if template has inspections
if template.inspections.count() > 0:
flash('Cannot delete template with existing inspections.', 'danger')
return redirect(url_for('templates.view_template', template_id=template.id))
template_name = template.name
db.session.delete(template)
db.session.commit()
flash(f'Template "{template_name}" deleted successfully.', 'success')
return redirect(url_for('templates.index'))
# Checklist Item Management
@bp.route('/<int:template_id>/items/new', methods=['GET', 'POST'])
@login_required
@supervisor_required
def create_checklist_item(template_id):
template = InspectionTemplate.query.get_or_404(template_id)
form = ChecklistItemForm()
if form.validate_on_submit():
# Get the highest display order
max_order = db.session.query(db.func.max(ChecklistItem.display_order))\
.filter_by(template_id=template.id).scalar() or 0
item = ChecklistItem(
template_id=template.id,
category=form.category.data,
item_description=form.item_description.data,
scoring_type=form.scoring_type.data,
weight=form.weight.data,
requires_photo=form.requires_photo.data,
display_order=max_order + 1
)
db.session.add(item)
db.session.commit()
flash('Checklist item added successfully.', 'success')
return redirect(url_for('templates.edit_template', template_id=template.id))
return render_template('templates/item_form.html', form=form, template=template, title='Add Checklist Item')
@bp.route('/items/<int:item_id>/edit', methods=['GET', 'POST'])
@login_required
@supervisor_required
def edit_checklist_item(item_id):
item = ChecklistItem.query.get_or_404(item_id)
form = ChecklistItemForm(obj=item)
if form.validate_on_submit():
item.category = form.category.data
item.item_description = form.item_description.data
item.scoring_type = form.scoring_type.data
item.weight = form.weight.data
item.requires_photo = form.requires_photo.data
db.session.commit()
flash('Checklist item updated successfully.', 'success')
return redirect(url_for('templates.edit_template', template_id=item.template_id))
return render_template('templates/item_form.html', form=form, item=item, template=item.template, title='Edit Checklist Item')
@bp.route('/items/<int:item_id>/delete', methods=['POST'])
@login_required
@supervisor_required
def delete_checklist_item(item_id):
item = ChecklistItem.query.get_or_404(item_id)
template_id = item.template_id
db.session.delete(item)
db.session.commit()
flash('Checklist item deleted successfully.', 'success')
return redirect(url_for('templates.edit_template', template_id=template_id))
@bp.route('/<int:template_id>/items/reorder', methods=['POST'])
@login_required
@supervisor_required
def reorder_items(template_id):
template = InspectionTemplate.query.get_or_404(template_id)
item_order = request.json.get('item_order', [])
for index, item_id in enumerate(item_order):
item = ChecklistItem.query.get(item_id)
if item and item.template_id == template.id:
item.display_order = index
db.session.commit()
return jsonify({'success': True})