Initial commit - Fire alarm management application
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
337
app/routes.py
Normal file
337
app/routes.py
Normal file
@@ -0,0 +1,337 @@
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for
|
||||
from .models import db, Job, Phase, Material
|
||||
from datetime import datetime
|
||||
from sqlalchemy import func
|
||||
|
||||
main_bp = Blueprint('main', __name__)
|
||||
api_bp = Blueprint('api', __name__)
|
||||
|
||||
|
||||
# ==================== WEB ROUTES ====================
|
||||
|
||||
@main_bp.route('/')
|
||||
def dashboard():
|
||||
return render_template('dashboard.html')
|
||||
|
||||
|
||||
@main_bp.route('/jobs')
|
||||
def jobs_list():
|
||||
return render_template('jobs.html')
|
||||
|
||||
|
||||
@main_bp.route('/jobs/<int:job_id>')
|
||||
def job_detail(job_id):
|
||||
job = Job.query.get_or_404(job_id)
|
||||
return render_template('job_detail.html', job=job)
|
||||
|
||||
|
||||
@main_bp.route('/jobs/new')
|
||||
def job_new():
|
||||
return render_template('job_form.html', job=None)
|
||||
|
||||
|
||||
@main_bp.route('/jobs/<int:job_id>/edit')
|
||||
def job_edit(job_id):
|
||||
job = Job.query.get_or_404(job_id)
|
||||
return render_template('job_form.html', job=job)
|
||||
|
||||
|
||||
@main_bp.route('/schedule')
|
||||
def schedule():
|
||||
return render_template('schedule.html')
|
||||
|
||||
|
||||
@main_bp.route('/materials')
|
||||
def materials():
|
||||
return render_template('materials.html')
|
||||
|
||||
|
||||
# ==================== API ROUTES ====================
|
||||
|
||||
# Dashboard Stats
|
||||
@api_bp.route('/stats')
|
||||
def get_stats():
|
||||
jobs = Job.query.all()
|
||||
|
||||
total_jobs = len(jobs)
|
||||
total_budget = sum(j.fire_alarm_budget or 0 for j in jobs)
|
||||
total_labor = sum(j.labor_estimate or 0 for j in jobs)
|
||||
total_material = sum(j.material_estimate or 0 for j in jobs)
|
||||
|
||||
# Calculate average completion
|
||||
avg_completion = sum(j.percent_complete or 0 for j in jobs) / total_jobs if total_jobs > 0 else 0
|
||||
|
||||
# Jobs by status
|
||||
completed_jobs = len([j for j in jobs if (j.percent_complete or 0) >= 1.0])
|
||||
in_progress_jobs = len([j for j in jobs if 0 < (j.percent_complete or 0) < 1.0])
|
||||
not_started_jobs = len([j for j in jobs if (j.percent_complete or 0) == 0])
|
||||
|
||||
# Budget by vendor
|
||||
vendor_budgets = {}
|
||||
for job in jobs:
|
||||
vendor = job.fire_vendor or 'Unknown'
|
||||
if vendor not in vendor_budgets:
|
||||
vendor_budgets[vendor] = 0
|
||||
vendor_budgets[vendor] += job.fire_alarm_budget or 0
|
||||
|
||||
# Jobs by PM
|
||||
pm_jobs = {}
|
||||
for job in jobs:
|
||||
pm = job.pm_assigned or 'Unassigned'
|
||||
if pm not in pm_jobs:
|
||||
pm_jobs[pm] = {'count': 0, 'budget': 0}
|
||||
pm_jobs[pm]['count'] += 1
|
||||
pm_jobs[pm]['budget'] += job.fire_alarm_budget or 0
|
||||
|
||||
return jsonify({
|
||||
'total_jobs': total_jobs,
|
||||
'total_budget': total_budget,
|
||||
'total_labor': total_labor,
|
||||
'total_material': total_material,
|
||||
'avg_completion': avg_completion * 100,
|
||||
'completed_jobs': completed_jobs,
|
||||
'in_progress_jobs': in_progress_jobs,
|
||||
'not_started_jobs': not_started_jobs,
|
||||
'vendor_budgets': vendor_budgets,
|
||||
'pm_jobs': pm_jobs,
|
||||
'jobs_completion': [
|
||||
{
|
||||
'id': j.id,
|
||||
'job_number': j.job_number,
|
||||
'name': j.job_name,
|
||||
'completion': (j.percent_complete or 0) * 100,
|
||||
'budget': j.fire_alarm_budget or 0,
|
||||
'pm_assigned': j.pm_assigned
|
||||
}
|
||||
for j in jobs
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
# Jobs CRUD
|
||||
@api_bp.route('/jobs', methods=['GET'])
|
||||
def get_jobs():
|
||||
jobs = Job.query.all()
|
||||
return jsonify([job.to_dict() for job in jobs])
|
||||
|
||||
|
||||
@api_bp.route('/jobs/<int:job_id>', methods=['GET'])
|
||||
def get_job(job_id):
|
||||
job = Job.query.get_or_404(job_id)
|
||||
return jsonify(job.to_dict())
|
||||
|
||||
|
||||
@api_bp.route('/jobs', methods=['POST'])
|
||||
def create_job():
|
||||
data = request.json
|
||||
|
||||
job = Job(
|
||||
job_number=data.get('job_number'),
|
||||
job_name=data.get('job_name'),
|
||||
location=data.get('location'),
|
||||
percent_complete=data.get('percent_complete', 0),
|
||||
est_starting_qtr=data.get('est_starting_qtr'),
|
||||
fire_alarm_budget=data.get('fire_alarm_budget', 0),
|
||||
labor_estimate=data.get('labor_estimate', 0),
|
||||
material_estimate=data.get('material_estimate', 0),
|
||||
amount_left_on_contract=data.get('amount_left_on_contract', 0),
|
||||
pm_assigned=data.get('pm_assigned'),
|
||||
aor=data.get('aor'),
|
||||
fire_vendor=data.get('fire_vendor'),
|
||||
install_partner=data.get('install_partner'),
|
||||
ps_or_install=data.get('ps_or_install'),
|
||||
subcontractor=data.get('subcontractor'),
|
||||
pci=data.get('pci'),
|
||||
voip_or_phone=data.get('voip_or_phone'),
|
||||
plans=data.get('plans'),
|
||||
notes=data.get('notes'),
|
||||
issues=data.get('issues'),
|
||||
milestone_1=data.get('milestone_1'),
|
||||
milestone_2=data.get('milestone_2'),
|
||||
milestone_3=data.get('milestone_3'),
|
||||
milestone_4=data.get('milestone_4'),
|
||||
milestone_5=data.get('milestone_5'),
|
||||
milestone_6=data.get('milestone_6'),
|
||||
milestone_7=data.get('milestone_7'),
|
||||
number_of_units=data.get('number_of_units'),
|
||||
sep_club_house=data.get('sep_club_house'),
|
||||
)
|
||||
|
||||
# Parse dates
|
||||
for date_field in ['elevator_final', 'pretest', 'final_date', 'co_drop_dead_date']:
|
||||
if data.get(date_field):
|
||||
try:
|
||||
setattr(job, date_field, datetime.strptime(data[date_field], '%Y-%m-%d').date())
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
db.session.add(job)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(job.to_dict()), 201
|
||||
|
||||
|
||||
@api_bp.route('/jobs/<int:job_id>', methods=['PUT'])
|
||||
def update_job(job_id):
|
||||
job = Job.query.get_or_404(job_id)
|
||||
data = request.json
|
||||
|
||||
# Update fields
|
||||
for field in ['job_number', 'job_name', 'location', 'percent_complete', 'est_starting_qtr',
|
||||
'fire_alarm_budget', 'labor_estimate', 'material_estimate', 'amount_left_on_contract',
|
||||
'pm_assigned', 'aor', 'fire_vendor', 'install_partner', 'ps_or_install',
|
||||
'subcontractor', 'pci', 'voip_or_phone', 'plans', 'notes', 'issues',
|
||||
'milestone_1', 'milestone_2', 'milestone_3', 'milestone_4',
|
||||
'milestone_5', 'milestone_6', 'milestone_7', 'number_of_units', 'sep_club_house']:
|
||||
if field in data:
|
||||
setattr(job, field, data[field])
|
||||
|
||||
# Parse dates
|
||||
for date_field in ['elevator_final', 'pretest', 'final_date', 'co_drop_dead_date']:
|
||||
if date_field in data:
|
||||
if data[date_field]:
|
||||
try:
|
||||
setattr(job, date_field, datetime.strptime(data[date_field], '%Y-%m-%d').date())
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
setattr(job, date_field, None)
|
||||
|
||||
db.session.commit()
|
||||
return jsonify(job.to_dict())
|
||||
|
||||
|
||||
@api_bp.route('/jobs/<int:job_id>', methods=['DELETE'])
|
||||
def delete_job(job_id):
|
||||
job = Job.query.get_or_404(job_id)
|
||||
db.session.delete(job)
|
||||
db.session.commit()
|
||||
return '', 204
|
||||
|
||||
|
||||
# Phases CRUD
|
||||
@api_bp.route('/jobs/<int:job_id>/phases', methods=['GET'])
|
||||
def get_phases(job_id):
|
||||
phases = Phase.query.filter_by(job_id=job_id).order_by(Phase.phase_type, Phase.phase_number).all()
|
||||
return jsonify([p.to_dict() for p in phases])
|
||||
|
||||
|
||||
@api_bp.route('/jobs/<int:job_id>/phases', methods=['POST'])
|
||||
def create_phase(job_id):
|
||||
Job.query.get_or_404(job_id)
|
||||
data = request.json
|
||||
|
||||
phase = Phase(
|
||||
job_id=job_id,
|
||||
phase_type=data.get('phase_type'),
|
||||
phase_number=data.get('phase_number'),
|
||||
points=data.get('points', 0),
|
||||
men_on_site=data.get('men_on_site', 0),
|
||||
completed=data.get('completed', False),
|
||||
due_date_met=data.get('due_date_met'),
|
||||
pci_man_hours=data.get('pci_man_hours', 0),
|
||||
rer_fire_mgmt_hours=data.get('rer_fire_mgmt_hours', 0),
|
||||
rer_fire_mgmt_hours_avl=data.get('rer_fire_mgmt_hours_avl', 0),
|
||||
)
|
||||
|
||||
for date_field in ['mobilization_date', 'start_date', 'due_date', 'completion_date']:
|
||||
if data.get(date_field):
|
||||
try:
|
||||
setattr(phase, date_field, datetime.strptime(data[date_field], '%Y-%m-%d').date())
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
db.session.add(phase)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(phase.to_dict()), 201
|
||||
|
||||
|
||||
@api_bp.route('/phases/<int:phase_id>', methods=['PUT'])
|
||||
def update_phase(phase_id):
|
||||
phase = Phase.query.get_or_404(phase_id)
|
||||
data = request.json
|
||||
|
||||
for field in ['phase_type', 'phase_number', 'points', 'men_on_site', 'completed',
|
||||
'due_date_met', 'pci_man_hours', 'rer_fire_mgmt_hours', 'rer_fire_mgmt_hours_avl']:
|
||||
if field in data:
|
||||
setattr(phase, field, data[field])
|
||||
|
||||
for date_field in ['mobilization_date', 'start_date', 'due_date', 'completion_date']:
|
||||
if date_field in data:
|
||||
if data[date_field]:
|
||||
try:
|
||||
setattr(phase, date_field, datetime.strptime(data[date_field], '%Y-%m-%d').date())
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
setattr(phase, date_field, None)
|
||||
|
||||
db.session.commit()
|
||||
return jsonify(phase.to_dict())
|
||||
|
||||
|
||||
@api_bp.route('/phases/<int:phase_id>', methods=['DELETE'])
|
||||
def delete_phase(phase_id):
|
||||
phase = Phase.query.get_or_404(phase_id)
|
||||
db.session.delete(phase)
|
||||
db.session.commit()
|
||||
return '', 204
|
||||
|
||||
|
||||
# Materials CRUD
|
||||
@api_bp.route('/jobs/<int:job_id>/materials', methods=['GET'])
|
||||
def get_materials(job_id):
|
||||
materials = Material.query.filter_by(job_id=job_id).all()
|
||||
return jsonify([m.to_dict() for m in materials])
|
||||
|
||||
|
||||
@api_bp.route('/materials', methods=['GET'])
|
||||
def get_all_materials():
|
||||
materials = Material.query.all()
|
||||
return jsonify([m.to_dict() for m in materials])
|
||||
|
||||
|
||||
@api_bp.route('/jobs/<int:job_id>/materials', methods=['POST'])
|
||||
def create_material(job_id):
|
||||
Job.query.get_or_404(job_id)
|
||||
data = request.json
|
||||
|
||||
material = Material(
|
||||
job_id=job_id,
|
||||
part_number=data.get('part_number'),
|
||||
quantity=data.get('quantity', 0),
|
||||
ordered=data.get('ordered', False),
|
||||
received=data.get('received', False),
|
||||
received_qty=data.get('received_qty', 0),
|
||||
received_by=data.get('received_by'),
|
||||
delivered_qty=data.get('delivered_qty', 0),
|
||||
delivered_to=data.get('delivered_to'),
|
||||
)
|
||||
|
||||
db.session.add(material)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(material.to_dict()), 201
|
||||
|
||||
|
||||
@api_bp.route('/materials/<int:material_id>', methods=['PUT'])
|
||||
def update_material(material_id):
|
||||
material = Material.query.get_or_404(material_id)
|
||||
data = request.json
|
||||
|
||||
for field in ['part_number', 'quantity', 'ordered', 'received', 'received_qty',
|
||||
'received_by', 'delivered_qty', 'delivered_to']:
|
||||
if field in data:
|
||||
setattr(material, field, data[field])
|
||||
|
||||
db.session.commit()
|
||||
return jsonify(material.to_dict())
|
||||
|
||||
|
||||
@api_bp.route('/materials/<int:material_id>', methods=['DELETE'])
|
||||
def delete_material(material_id):
|
||||
material = Material.query.get_or_404(material_id)
|
||||
db.session.delete(material)
|
||||
db.session.commit()
|
||||
return '', 204
|
||||
Reference in New Issue
Block a user