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)