feat(case): enable editing and close/reopen actions on case detail

- Add POST /case/{id}/update route for editing case fields (status, case_type, description, open_date, close_date)
- Add POST /case/{id}/close route to set status='closed' and close_date=current date
- Add POST /case/{id}/reopen route to set status='active' and clear close_date
- Update case.html template with edit form, success/error messaging, and action buttons
- Include comprehensive validation for dates and status values
- Add proper error handling with session-based error storage
- Preserve existing view content and styling consistency
This commit is contained in:
HotSwapp
2025-10-06 19:43:21 -05:00
parent 2e49340663
commit 728d26ad17
2 changed files with 290 additions and 1 deletions

View File

@@ -8,8 +8,10 @@ and provides the main application instance.
import os
import logging
from contextlib import asynccontextmanager
from datetime import datetime
from typing import Optional
from fastapi import FastAPI, Depends, Request, Query
from fastapi import FastAPI, Depends, Request, Query, HTTPException
from fastapi.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
from fastapi.middleware.cors import CORSMiddleware
@@ -320,6 +322,7 @@ async def admin_panel(request: Request, db: Session = Depends(get_db)):
async def case_detail(
request: Request,
case_id: int,
saved: bool = Query(False, description="Whether to show success message"),
db: Session = Depends(get_db),
):
"""
@@ -348,6 +351,9 @@ async def case_detail(
if not case_obj:
logger.warning("Case not found: id=%s", case_id)
# Get any errors from session and clear them
errors = request.session.pop("case_update_errors", None)
return templates.TemplateResponse(
"case.html",
{
@@ -355,17 +361,203 @@ async def case_detail(
"user": user,
"case": None,
"error": "Case not found",
"saved": False,
"errors": errors or [],
},
status_code=404,
)
logger.info("Rendering case detail: id=%s, file_no='%s'", case_obj.id, case_obj.file_no)
# Get any errors from session and clear them
errors = request.session.pop("case_update_errors", None)
return templates.TemplateResponse(
"case.html",
{
"request": request,
"user": user,
"case": case_obj,
"saved": saved,
"errors": errors or [],
},
)
@app.post("/case/{case_id}/update")
async def case_update(
request: Request,
case_id: int,
status: str = None,
case_type: str = None,
description: str = None,
open_date: str = None,
close_date: str = None,
db: Session = Depends(get_db),
) -> RedirectResponse:
"""
Update case details.
Updates the specified fields on a case and redirects back to the case detail view.
"""
# Check authentication
user = get_current_user_from_session(request.session)
if not user:
return RedirectResponse(url="/login", status_code=302)
# Fetch the case
case_obj = db.query(Case).filter(Case.id == case_id).first()
if not case_obj:
logger.warning("Case not found for update: id=%s", case_id)
return RedirectResponse(url=f"/case/{case_id}", status_code=302)
# Validate and process fields
errors = []
update_data = {}
# Status validation
if status is not None:
if status not in ["active", "closed"]:
errors.append("Status must be 'active' or 'closed'")
else:
update_data["status"] = status
# Case type and description (optional)
if case_type is not None:
update_data["case_type"] = case_type.strip() if case_type.strip() else None
if description is not None:
update_data["description"] = description.strip() if description.strip() else None
# Date validation and parsing
if open_date is not None:
if open_date.strip():
try:
update_data["open_date"] = datetime.strptime(open_date.strip(), "%Y-%m-%d")
except ValueError:
errors.append("Open date must be in YYYY-MM-DD format")
else:
update_data["open_date"] = None
if close_date is not None:
if close_date.strip():
try:
update_data["close_date"] = datetime.strptime(close_date.strip(), "%Y-%m-%d")
except ValueError:
errors.append("Close date must be in YYYY-MM-DD format")
else:
update_data["close_date"] = None
# If there are validation errors, redirect back with errors
if errors:
# Store errors in session for display on the case page
request.session["case_update_errors"] = errors
return RedirectResponse(url=f"/case/{case_id}", status_code=302)
# Apply updates
try:
for field, value in update_data.items():
setattr(case_obj, field, value)
db.commit()
logger.info("Case updated successfully: id=%s, fields=%s", case_id, list(update_data.keys()))
# Clear any previous errors from session
request.session.pop("case_update_errors", None)
return RedirectResponse(url=f"/case/{case_id}?saved=1", status_code=302)
except Exception as e:
db.rollback()
logger.error("Failed to update case id=%s: %s", case_id, str(e))
# Store error in session for display
request.session["case_update_errors"] = ["Failed to save changes. Please try again."]
return RedirectResponse(url=f"/case/{case_id}", status_code=302)
@app.post("/case/{case_id}/close")
async def case_close(
request: Request,
case_id: int,
db: Session = Depends(get_db),
) -> RedirectResponse:
"""
Close a case.
Sets the case status to 'closed' and sets close_date to current date if not already set.
"""
# Check authentication
user = get_current_user_from_session(request.session)
if not user:
return RedirectResponse(url="/login", status_code=302)
# Fetch the case
case_obj = db.query(Case).filter(Case.id == case_id).first()
if not case_obj:
logger.warning("Case not found for close: id=%s", case_id)
return RedirectResponse(url=f"/case/{case_id}", status_code=302)
# Update case
try:
case_obj.status = "closed"
# Only set close_date if it's not already set
if not case_obj.close_date:
case_obj.close_date = datetime.now()
db.commit()
logger.info("Case closed: id=%s, close_date=%s", case_id, case_obj.close_date)
return RedirectResponse(url=f"/case/{case_id}?saved=1", status_code=302)
except Exception as e:
db.rollback()
logger.error("Failed to close case id=%s: %s", case_id, str(e))
# Store error in session for display
request.session["case_update_errors"] = ["Failed to close case. Please try again."]
return RedirectResponse(url=f"/case/{case_id}", status_code=302)
@app.post("/case/{case_id}/reopen")
async def case_reopen(
request: Request,
case_id: int,
db: Session = Depends(get_db),
) -> RedirectResponse:
"""
Reopen a case.
Sets the case status to 'active' and clears the close_date.
"""
# Check authentication
user = get_current_user_from_session(request.session)
if not user:
return RedirectResponse(url="/login", status_code=302)
# Fetch the case
case_obj = db.query(Case).filter(Case.id == case_id).first()
if not case_obj:
logger.warning("Case not found for reopen: id=%s", case_id)
return RedirectResponse(url=f"/case/{case_id}", status_code=302)
# Update case
try:
case_obj.status = "active"
case_obj.close_date = None
db.commit()
logger.info("Case reopened: id=%s", case_id)
return RedirectResponse(url=f"/case/{case_id}?saved=1", status_code=302)
except Exception as e:
db.rollback()
logger.error("Failed to reopen case id=%s: %s", case_id, str(e))
# Store error in session for display
request.session["case_update_errors"] = ["Failed to reopen case. Please try again."]
return RedirectResponse(url=f"/case/{case_id}", status_code=302)