@cryptotaxi247 / CoPilot / commits / dbdf39c1

529 closing a case does not close its merged alerts (#533)

* Enhance case status update to automatically manage linked alert statuses * precommit-fixes

taylor_socfortress committed Nov 30, 2025 at 13:12 UTC dbdf39c159bbb93e3fd1b8ef06b3a9aaa2542a9a
1 file changed +76 -5
backend/app/incidents/routes/db_operations.py
+76 -5
@@ -29,6 +29,7 @@ from app.data_store.data_store_operations import (
29 from app.db.db_session import get_db
30 from app.db.universal_models import Customers
31 from app.incidents.models import Alert
32 +from app.incidents.models import CaseAlertLink
33 from app.incidents.models import CaseComment
34 from app.incidents.models import Comment
35 from app.incidents.models import FieldName
@@ -1335,8 +1336,8 @@ async def update_case_status_endpoint(
1336 current_user: User = Depends(AuthHandler().get_current_user),
1337 db: AsyncSession = Depends(get_db),
1338 ):
1338 - """Update case status with customer access validation"""
1339 - logger.info(f"Updating case {case_status.case_id} status for user: {current_user.username} with role_id: {current_user.role_id}")
1339 + """Update case status with customer access validation and auto-update linked alerts"""
1340 + logger.info(f"Updating case {case_status.case_id} status to {case_status.status} for user: {current_user.username}")
1341
1342 # Get the case first to check customer access
1343 case = await get_case_by_id(case_status.case_id, db)
@@ -1345,12 +1346,82 @@ async def update_case_status_endpoint(
1346 if not await customer_access_handler.check_customer_access(current_user, case.customer_code, db):
1347 raise HTTPException(status_code=403, detail=f"Access denied to case {case_status.case_id} - insufficient customer permissions")
1348
1348 - # Update the case status
1349 - await update_case_status(case_status, db)
1349 + # Store the old status BEFORE updating - this is the actual current status from the database
1350 + old_status = case.case_status
1351 +
1352 + # Convert new status enum to string value for comparison
1353 + new_status_value = case_status.status.value if hasattr(case_status.status, "value") else str(case_status.status)
1354 +
1355 + logger.info(f"Case status transition: {old_status} -> {new_status_value}")
1356 +
1357 + try:
1358 + # Get all alert IDs linked to this case BEFORE updating
1359 + result = await db.execute(select(CaseAlertLink.alert_id).where(CaseAlertLink.case_id == case_status.case_id))
1360 + alert_ids = [row[0] for row in result]
1361 +
1362 + logger.info(f"Found {len(alert_ids)} alerts linked to case {case_status.case_id}")
1363 +
1364 + # Determine what to do with linked alerts based on status transition
1365 + new_alert_status = None
1366 +
1367 + # Handle status transitions
1368 + if new_status_value == "CLOSED" and (old_status != "CLOSED" or old_status is None):
1369 + # Case is being closed - close all linked alerts
1370 + logger.info(f"Closing {len(alert_ids)} alerts linked to case {case_status.case_id}")
1371 + new_alert_status = "CLOSED"
1372 +
1373 + elif old_status == "CLOSED" and new_status_value in ["OPEN", "IN_PROGRESS"]:
1374 + # Case is being reopened from CLOSED - reopen alerts to IN_PROGRESS
1375 + logger.info(f"Reopening {len(alert_ids)} alerts linked to case {case_status.case_id} to IN_PROGRESS")
1376 + new_alert_status = "IN_PROGRESS"
1377 +
1378 + elif old_status == "CLOSED" and new_status_value != "CLOSED":
1379 + # Case is being reopened from CLOSED to any other status - reopen to IN_PROGRESS
1380 + logger.info(f"Reopening {len(alert_ids)} alerts linked to case {case_status.case_id} to IN_PROGRESS")
1381 + new_alert_status = "IN_PROGRESS"
1382 +
1383 + else:
1384 + # No alert status change needed for other transitions
1385 + logger.info(f"No alert status change needed for transition from {old_status} to {new_status_value}")
1386 +
1387 + # Update alert statuses if needed (BEFORE updating the case)
1388 + if new_alert_status:
1389 + closed_count = 0
1390 + failed_alerts = []
1391 +
1392 + for alert_id in alert_ids:
1393 + try:
1394 + logger.debug(f"Updating alert {alert_id} to {new_alert_status}")
1395 + await update_alert_status(UpdateAlertStatus(alert_id=alert_id, status=new_alert_status), db)
1396 + closed_count += 1
1397 + except Exception as e:
1398 + logger.error(f"Failed to update alert {alert_id}: {str(e)}")
1399 + failed_alerts.append(alert_id)
1400 +
1401 + logger.info(f"Successfully updated {closed_count}/{len(alert_ids)} alerts to {new_alert_status}")
1402 +
1403 + if failed_alerts:
1404 + logger.warning(f"Failed to update alerts: {failed_alerts}")
1405 +
1406 + # NOW update the case status (after we've handled the alerts)
1407 + await update_case_status(case_status, db)
1408 +
1409 + # Commit all changes
1410 + await db.commit()
1411 +
1412 + except Exception as e:
1413 + logger.error(f"Error updating case status: {str(e)}")
1414 + await db.rollback()
1415 + raise HTTPException(status_code=500, detail=f"Failed to update case status: {str(e)}")
1416
1417 # Re-fetch the case with full data structure
1418 updated_case = await get_case_by_id(case_status.case_id, db)
1353 - return CaseOutResponse(cases=[updated_case], success=True, message="Case status updated successfully")
1419 +
1420 + message = "Case status updated successfully"
1421 + if alert_ids and new_alert_status:
1422 + message += f" and {len(alert_ids)} linked alerts updated to {new_alert_status}"
1423 +
1424 + return CaseOutResponse(cases=[updated_case], success=True, message=message)
1425
1426
1427 @incidents_db_operations_router.put("/case/escalated", response_model=CaseOutResponse)