From 728d26ad17fb29a8f23d43f522187e6fdcbcba9a Mon Sep 17 00:00:00 2001 From: HotSwapp <47397945+HotSwapp@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:43:21 -0500 Subject: [PATCH] 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 --- app/main.py | 194 +++++++++++++++++++++++++++++++++++++++- app/templates/case.html | 97 ++++++++++++++++++++ 2 files changed, 290 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index d291338..7cd6c97 100644 --- a/app/main.py +++ b/app/main.py @@ -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) diff --git a/app/templates/case.html b/app/templates/case.html index 5c3b4b7..db04f1a 100644 --- a/app/templates/case.html +++ b/app/templates/case.html @@ -20,6 +20,31 @@ Case {{ case.file_no if case else '' }} · Delphi Database {% endif %} + {% if saved %} +
+ +
+ {% endif %} + + {% if errors %} +
+ +
+ {% endif %} + {% if case %}
@@ -83,6 +108,78 @@ Case {{ case.file_no if case else '' }} · Delphi Database
+ +
+
+
+
Edit Case
+
+ {% if case.status == 'active' %} +
+ +
+ {% endif %} + {% if case.status == 'closed' %} +
+ +
+ {% endif %} +
+
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + Cancel +
+
+
+
+
+
+
+
Transactions