from fastapi import FastAPI, Request, Depends, HTTPException, Form from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from typing import Optional from datetime import date from decimal import Decimal import models import schemas import crud from database import engine, get_db # Create tables models.Base.metadata.create_all(bind=engine) app = FastAPI(title="Architecture PM System") # Mount static files app.mount("/static", StaticFiles(directory="static"), name="static") # Templates templates = Jinja2Templates(directory="templates") # ============================================================================ # HOME PAGE # ============================================================================ @app.get("/", response_class=HTMLResponse) async def home(request: Request, db: Session = Depends(get_db)): projects_count = db.query(models.Project).count() employees_count = db.query(models.Employee).count() timesheets_count = db.query(models.Timesheet).count() invoices_count = db.query(models.Invoice).count() return templates.TemplateResponse("index.html", { "request": request, "projects_count": projects_count, "employees_count": employees_count, "timesheets_count": timesheets_count, "invoices_count": invoices_count }) # ============================================================================ # PROJECTS # ============================================================================ @app.get("/projects", response_class=HTMLResponse) async def list_projects(request: Request, db: Session = Depends(get_db)): projects = crud.get_projects(db) return templates.TemplateResponse("projects.html", { "request": request, "projects": projects }) @app.get("/api/projects") async def api_list_projects(db: Session = Depends(get_db)): return crud.get_projects(db) @app.post("/projects/create") async def create_project( project_id: str = Form(...), project_name: str = Form(...), client_name: str = Form(...), contract_value_aed: float = Form(...), planned_cost_aed: float = Form(...), project_start_date: date = Form(...), project_end_date: date = Form(...), project_type: str = Form(...), project_status: str = Form(...), current_phase: Optional[str] = Form(None), db: Session = Depends(get_db) ): project = schemas.ProjectCreate( project_id=project_id, project_name=project_name, client_name=client_name, contract_value_aed=Decimal(str(contract_value_aed)), planned_cost_aed=Decimal(str(planned_cost_aed)), project_start_date=project_start_date, project_end_date=project_end_date, project_type=project_type, project_status=project_status, current_phase=current_phase ) crud.create_project(db, project) return RedirectResponse(url="/projects", status_code=303) @app.post("/projects/delete/{project_id}") async def delete_project(project_id: str, db: Session = Depends(get_db)): crud.delete_project(db, project_id) return RedirectResponse(url="/projects", status_code=303) # ============================================================================ # EMPLOYEES # ============================================================================ @app.get("/employees", response_class=HTMLResponse) async def list_employees(request: Request, db: Session = Depends(get_db)): employees = crud.get_employees(db) return templates.TemplateResponse("employees.html", { "request": request, "employees": employees }) @app.get("/api/employees") async def api_list_employees(db: Session = Depends(get_db)): return crud.get_employees(db) @app.post("/employees/create") async def create_employee( employee_id: str = Form(...), employee_name: str = Form(...), department: str = Form(...), role: str = Form(...), hourly_rate_aed: float = Form(...), employment_type: str = Form(...), start_date: date = Form(...), cost_category: str = Form(...), email: Optional[str] = Form(None), phone: Optional[str] = Form(None), db: Session = Depends(get_db) ): employee = schemas.EmployeeCreate( employee_id=employee_id, employee_name=employee_name, department=department, role=role, hourly_rate_aed=Decimal(str(hourly_rate_aed)), employment_type=employment_type, start_date=start_date, cost_category=cost_category, email=email, phone=phone ) crud.create_employee(db, employee) return RedirectResponse(url="/employees", status_code=303) @app.post("/employees/delete/{employee_id}") async def delete_employee(employee_id: str, db: Session = Depends(get_db)): crud.delete_employee(db, employee_id) return RedirectResponse(url="/employees", status_code=303) # ============================================================================ # TIMESHEETS # ============================================================================ @app.get("/timesheets", response_class=HTMLResponse) async def list_timesheets(request: Request, db: Session = Depends(get_db)): timesheets = crud.get_timesheets(db, limit=200) employees = crud.get_employees(db) projects = crud.get_projects(db) return templates.TemplateResponse("timesheets.html", { "request": request, "timesheets": timesheets, "employees": employees, "projects": projects }) @app.get("/api/timesheets") async def api_list_timesheets(db: Session = Depends(get_db)): return crud.get_timesheets(db, limit=200) @app.post("/timesheets/create") async def create_timesheet( record_id: str = Form(...), date: date = Form(...), employee_id: str = Form(...), project_id: str = Form(...), hours_worked: float = Form(...), billable_hours: float = Form(...), work_category: str = Form(...), task_description: Optional[str] = Form(None), db: Session = Depends(get_db) ): timesheet = schemas.TimesheetCreate( record_id=record_id, date=date, employee_id=employee_id, project_id=project_id, hours_worked=Decimal(str(hours_worked)), billable_hours=Decimal(str(billable_hours)), work_category=work_category, task_description=task_description ) crud.create_timesheet(db, timesheet) return RedirectResponse(url="/timesheets", status_code=303) @app.post("/timesheets/delete/{record_id}") async def delete_timesheet(record_id: str, db: Session = Depends(get_db)): crud.delete_timesheet(db, record_id) return RedirectResponse(url="/timesheets", status_code=303) # ============================================================================ # MILESTONES # ============================================================================ @app.get("/milestones", response_class=HTMLResponse) async def list_milestones(request: Request, db: Session = Depends(get_db)): milestones = crud.get_milestones(db) projects = crud.get_projects(db) return templates.TemplateResponse("milestones.html", { "request": request, "milestones": milestones, "projects": projects }) @app.get("/api/milestones") async def api_list_milestones(db: Session = Depends(get_db)): return crud.get_milestones(db) @app.post("/milestones/create") async def create_milestone( project_id: str = Form(...), milestone_name: str = Form(...), milestone_order: int = Form(...), planned_date: date = Form(...), status: str = Form(...), completion_percentage: int = Form(0), actual_date: Optional[date] = Form(None), db: Session = Depends(get_db) ): milestone = schemas.MilestoneCreate( project_id=project_id, milestone_name=milestone_name, milestone_order=milestone_order, planned_date=planned_date, actual_date=actual_date, status=status, completion_percentage=completion_percentage ) crud.create_milestone(db, milestone) return RedirectResponse(url="/milestones", status_code=303) @app.post("/milestones/delete/{milestone_id}") async def delete_milestone(milestone_id: int, db: Session = Depends(get_db)): crud.delete_milestone(db, milestone_id) return RedirectResponse(url="/milestones", status_code=303) # ============================================================================ # INVOICES # ============================================================================ @app.get("/invoices", response_class=HTMLResponse) async def list_invoices(request: Request, db: Session = Depends(get_db)): invoices = crud.get_invoices(db) projects = crud.get_projects(db) return templates.TemplateResponse("invoices.html", { "request": request, "invoices": invoices, "projects": projects }) @app.get("/api/invoices") async def api_list_invoices(db: Session = Depends(get_db)): return crud.get_invoices(db) @app.post("/invoices/create") async def create_invoice( invoice_id: str = Form(...), project_id: str = Form(...), invoice_date: date = Form(...), invoice_amount_aed: float = Form(...), due_date: date = Form(...), payment_status: str = Form(...), payment_date: Optional[date] = Form(None), milestone_reference: Optional[str] = Form(None), db: Session = Depends(get_db) ): invoice = schemas.InvoiceCreate( invoice_id=invoice_id, project_id=project_id, invoice_date=invoice_date, invoice_amount_aed=Decimal(str(invoice_amount_aed)), due_date=due_date, payment_date=payment_date, payment_status=payment_status, milestone_reference=milestone_reference ) crud.create_invoice(db, invoice) return RedirectResponse(url="/invoices", status_code=303) @app.post("/invoices/delete/{invoice_id}") async def delete_invoice(invoice_id: str, db: Session = Depends(get_db)): crud.delete_invoice(db, invoice_id) return RedirectResponse(url="/invoices", status_code=303) # ============================================================================ # SUBCONTRACTORS # ============================================================================ @app.get("/subcontractors", response_class=HTMLResponse) async def list_subcontractors(request: Request, db: Session = Depends(get_db)): subcontractors = crud.get_subcontractors(db) projects = crud.get_projects(db) return templates.TemplateResponse("subcontractors.html", { "request": request, "subcontractors": subcontractors, "projects": projects }) @app.get("/api/subcontractors") async def api_list_subcontractors(db: Session = Depends(get_db)): return crud.get_subcontractors(db) @app.post("/subcontractors/create") async def create_subcontractor( project_id: str = Form(...), subcontractor_name: str = Form(...), service_type: str = Form(...), contract_amount_aed: float = Form(...), amount_invoiced_aed: float = Form(0), payment_status: str = Form(...), work_category: str = Form(...), contact_person: Optional[str] = Form(None), contact_email: Optional[str] = Form(None), db: Session = Depends(get_db) ): subcontractor = schemas.SubcontractorCreate( project_id=project_id, subcontractor_name=subcontractor_name, service_type=service_type, contract_amount_aed=Decimal(str(contract_amount_aed)), amount_invoiced_aed=Decimal(str(amount_invoiced_aed)), payment_status=payment_status, work_category=work_category, contact_person=contact_person, contact_email=contact_email ) crud.create_subcontractor(db, subcontractor) return RedirectResponse(url="/subcontractors", status_code=303) @app.post("/subcontractors/delete/{subcontractor_id}") async def delete_subcontractor(subcontractor_id: int, db: Session = Depends(get_db)): crud.delete_subcontractor(db, subcontractor_id) return RedirectResponse(url="/subcontractors", status_code=303) # ============================================================================ # STAFF ALLOCATION # ============================================================================ @app.get("/staff-allocation", response_class=HTMLResponse) async def list_staff_allocations(request: Request, db: Session = Depends(get_db)): allocations = crud.get_staff_allocations(db) projects = crud.get_projects(db) employees = crud.get_employees(db) milestones = crud.get_milestones(db) return templates.TemplateResponse("staff_allocation.html", { "request": request, "allocations": allocations, "projects": projects, "employees": employees, "milestones": milestones }) @app.get("/api/staff-allocation") async def api_list_staff_allocations(db: Session = Depends(get_db)): return crud.get_staff_allocations(db) @app.post("/staff-allocation/create") async def create_staff_allocation( project_id: str = Form(...), employee_id: str = Form(...), employee_name: str = Form(...), role: str = Form(...), hours_allocated: float = Form(...), hours_worked: float = Form(0), hourly_rate_aed: float = Form(...), start_date: date = Form(...), milestone_id: Optional[int] = Form(None), milestone_name: Optional[str] = Form(None), category: Optional[str] = Form(None), skill_match_score: Optional[int] = Form(None), availability_status: Optional[str] = Form(None), performance_rating: Optional[str] = Form(None), end_date: Optional[date] = Form(None), notes: Optional[str] = Form(None), db: Session = Depends(get_db) ): allocation = schemas.StaffAllocationCreate( project_id=project_id, milestone_id=milestone_id, milestone_name=milestone_name, employee_id=employee_id, employee_name=employee_name, role=role, category=category, hours_allocated=Decimal(str(hours_allocated)), hours_worked=Decimal(str(hours_worked)), hourly_rate_aed=Decimal(str(hourly_rate_aed)), skill_match_score=skill_match_score, availability_status=availability_status, performance_rating=performance_rating, start_date=start_date, end_date=end_date, notes=notes ) crud.create_staff_allocation(db, allocation) return RedirectResponse(url="/staff-allocation", status_code=303) @app.post("/staff-allocation/delete/{allocation_id}") async def delete_staff_allocation(allocation_id: int, db: Session = Depends(get_db)): crud.delete_staff_allocation(db, allocation_id) return RedirectResponse(url="/staff-allocation", status_code=303) # ============================================================================ # PROJECTS - UPDATE # ============================================================================ @app.get("/projects/edit/{project_id}", response_class=HTMLResponse) async def edit_project_form(request: Request, project_id: str, db: Session = Depends(get_db)): project = crud.get_project(db, project_id) if not project: raise HTTPException(status_code=404, detail="Project not found") return templates.TemplateResponse("edit_project.html", { "request": request, "project": project }) @app.post("/projects/update/{project_id}") async def update_project( project_id: str, project_name: str = Form(...), client_name: str = Form(...), contract_value_aed: float = Form(...), planned_cost_aed: float = Form(...), project_start_date: date = Form(...), project_end_date: date = Form(...), project_type: str = Form(...), project_status: str = Form(...), current_phase: Optional[str] = Form(None), db: Session = Depends(get_db) ): project = schemas.ProjectCreate( project_id=project_id, project_name=project_name, client_name=client_name, contract_value_aed=Decimal(str(contract_value_aed)), planned_cost_aed=Decimal(str(planned_cost_aed)), project_start_date=project_start_date, project_end_date=project_end_date, project_type=project_type, project_status=project_status, current_phase=current_phase ) crud.update_project(db, project_id, project) return RedirectResponse(url="/projects", status_code=303) # ============================================================================ # EMPLOYEES - UPDATE # ============================================================================ @app.post("/employees/update/{employee_id}") async def update_employee( employee_id: str, employee_name: str = Form(...), department: str = Form(...), role: str = Form(...), hourly_rate_aed: float = Form(...), employment_type: str = Form(...), start_date: date = Form(...), cost_category: str = Form(...), email: Optional[str] = Form(None), phone: Optional[str] = Form(None), db: Session = Depends(get_db) ): employee = schemas.EmployeeCreate( employee_id=employee_id, employee_name=employee_name, department=department, role=role, hourly_rate_aed=Decimal(str(hourly_rate_aed)), employment_type=employment_type, start_date=start_date, cost_category=cost_category, email=email, phone=phone ) crud.update_employee(db, employee_id, employee) return RedirectResponse(url="/employees", status_code=303) # ============================================================================ # TIMESHEETS - UPDATE # ============================================================================ @app.post("/timesheets/update/{record_id}") async def update_timesheet( record_id: str, date: date = Form(...), employee_id: str = Form(...), project_id: str = Form(...), hours_worked: float = Form(...), billable_hours: float = Form(...), work_category: str = Form(...), task_description: Optional[str] = Form(None), db: Session = Depends(get_db) ): timesheet = schemas.TimesheetCreate( record_id=record_id, date=date, employee_id=employee_id, project_id=project_id, hours_worked=Decimal(str(hours_worked)), billable_hours=Decimal(str(billable_hours)), work_category=work_category, task_description=task_description ) crud.update_timesheet(db, record_id, timesheet) return RedirectResponse(url="/timesheets", status_code=303) # ============================================================================ # MILESTONES - UPDATE # ============================================================================ @app.post("/milestones/update/{milestone_id}") async def update_milestone( milestone_id: int, project_id: str = Form(...), milestone_name: str = Form(...), milestone_order: int = Form(...), planned_date: date = Form(...), status: str = Form(...), completion_percentage: int = Form(0), actual_date: Optional[date] = Form(None), db: Session = Depends(get_db) ): milestone = schemas.MilestoneCreate( project_id=project_id, milestone_name=milestone_name, milestone_order=milestone_order, planned_date=planned_date, actual_date=actual_date, status=status, completion_percentage=completion_percentage ) crud.update_milestone(db, milestone_id, milestone) return RedirectResponse(url="/milestones", status_code=303) # ============================================================================ # INVOICES - UPDATE # ============================================================================ @app.post("/invoices/update/{invoice_id}") async def update_invoice( invoice_id: str, project_id: str = Form(...), invoice_date: date = Form(...), invoice_amount_aed: float = Form(...), due_date: date = Form(...), payment_status: str = Form(...), payment_date: Optional[date] = Form(None), milestone_reference: Optional[str] = Form(None), db: Session = Depends(get_db) ): invoice = schemas.InvoiceCreate( invoice_id=invoice_id, project_id=project_id, invoice_date=invoice_date, invoice_amount_aed=Decimal(str(invoice_amount_aed)), due_date=due_date, payment_date=payment_date, payment_status=payment_status, milestone_reference=milestone_reference ) crud.update_invoice(db, invoice_id, invoice) return RedirectResponse(url="/invoices", status_code=303) # ============================================================================ # SUBCONTRACTORS - UPDATE # ============================================================================ @app.post("/subcontractors/update/{subcontractor_id}") async def update_subcontractor( subcontractor_id: int, project_id: str = Form(...), subcontractor_name: str = Form(...), service_type: str = Form(...), contract_amount_aed: float = Form(...), amount_invoiced_aed: float = Form(0), payment_status: str = Form(...), work_category: str = Form(...), contact_person: Optional[str] = Form(None), contact_email: Optional[str] = Form(None), db: Session = Depends(get_db) ): subcontractor = schemas.SubcontractorCreate( project_id=project_id, subcontractor_name=subcontractor_name, service_type=service_type, contract_amount_aed=Decimal(str(contract_amount_aed)), amount_invoiced_aed=Decimal(str(amount_invoiced_aed)), payment_status=payment_status, work_category=work_category, contact_person=contact_person, contact_email=contact_email ) crud.update_subcontractor(db, subcontractor_id, subcontractor) return RedirectResponse(url="/subcontractors", status_code=303) # ============================================================================ # STAFF ALLOCATION - UPDATE # ============================================================================ @app.post("/staff-allocation/update/{allocation_id}") async def update_staff_allocation( allocation_id: int, project_id: str = Form(...), employee_id: str = Form(...), employee_name: str = Form(...), role: str = Form(...), hours_allocated: float = Form(...), hours_worked: float = Form(0), hourly_rate_aed: float = Form(...), start_date: date = Form(...), milestone_id: Optional[int] = Form(None), milestone_name: Optional[str] = Form(None), category: Optional[str] = Form(None), skill_match_score: Optional[int] = Form(None), availability_status: Optional[str] = Form(None), performance_rating: Optional[str] = Form(None), end_date: Optional[date] = Form(None), notes: Optional[str] = Form(None), db: Session = Depends(get_db) ): allocation = schemas.StaffAllocationCreate( project_id=project_id, milestone_id=milestone_id, milestone_name=milestone_name, employee_id=employee_id, employee_name=employee_name, role=role, category=category, hours_allocated=Decimal(str(hours_allocated)), hours_worked=Decimal(str(hours_worked)), hourly_rate_aed=Decimal(str(hourly_rate_aed)), skill_match_score=skill_match_score, availability_status=availability_status, performance_rating=performance_rating, start_date=start_date, end_date=end_date, notes=notes ) crud.update_staff_allocation(db, allocation_id, allocation) return RedirectResponse(url="/staff-allocation", status_code=303) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)