From ac3bf97920689c4a50253123499a89bd9b5e7723 Mon Sep 17 00:00:00 2001 From: OG T Date: Mon, 23 Mar 2026 21:37:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(api):=20=E7=B0=BD=E6=A0=B8=E5=BE=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Incident=20=E7=8B=80=E6=85=8B=E7=82=BA=20R?= =?UTF-8?q?ESOLVED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: 簽核成功後 Incident.status 未更新,導致刷新頁面後 Y/n 按鈕重現 修復: - proposal_service.py: 新增 resolve_incident_after_approval() 方法 - approvals.py: sign_approval 成功後呼叫更新 Incident 狀態 - 使用 metadata.incident_id 反查關聯的 Incident Co-Authored-By: Claude Opus 4.5 --- apps/api/src/api/v1/approvals.py | 15 ++++++ apps/api/src/services/proposal_service.py | 64 +++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/apps/api/src/api/v1/approvals.py b/apps/api/src/api/v1/approvals.py index 96793e928..9045828eb 100644 --- a/apps/api/src/api/v1/approvals.py +++ b/apps/api/src/api/v1/approvals.py @@ -43,6 +43,7 @@ from src.models.approval import ( SignResponse, ) from src.services.executor import OperationType, get_executor +from src.services.proposal_service import get_proposal_service router = APIRouter(prefix="/approvals", tags=["HITL Approvals"]) logger = get_logger("awoooi.approvals") @@ -629,6 +630,20 @@ async def sign_approval( background_tasks.add_task(execute_approved_action, approval) + # Phase 6.5: 更新關聯的 Incident 狀態為 RESOLVED + incident_id = approval.metadata.get("incident_id") if approval.metadata else None + if incident_id: + proposal_svc = get_proposal_service() + await proposal_svc.resolve_incident_after_approval( + incident_id=incident_id, + approval_id=str(approval_id), + ) + logger.info( + "incident_resolved_after_sign", + incident_id=incident_id, + approval_id=str(approval_id), + ) + return SignResponse( success=True, message=message, diff --git a/apps/api/src/services/proposal_service.py b/apps/api/src/services/proposal_service.py index 585057c62..1fc45edc5 100644 --- a/apps/api/src/services/proposal_service.py +++ b/apps/api/src/services/proposal_service.py @@ -501,6 +501,70 @@ class ProposalService: error=str(e), ) + # ========================================================================= + # Phase 6.5: 簽核完成後更新 Incident 狀態 + # ========================================================================= + + async def resolve_incident_after_approval( + self, + incident_id: str, + approval_id: str | None = None, + ) -> bool: + """ + 簽核完成後更新 Incident 狀態為 RESOLVED + + 當 Approval 達到所需簽核數時呼叫,更新: + 1. incident.status → RESOLVED + 2. incident.decision.state → completed (如果有) + + Args: + incident_id: Incident ID + approval_id: 簽核的 Approval ID (用於日誌) + + Returns: + 是否更新成功 + """ + try: + incident = await self._load_incident(incident_id) + if not incident: + logger.warning( + "resolve_incident_not_found", + incident_id=incident_id, + approval_id=approval_id, + ) + return False + + # 更新狀態 + old_status = incident.status + incident.status = IncidentStatus.RESOLVED + incident.updated_at = datetime.now(timezone.utc) + + # 更新 decision.state (如果有) + if incident.decision: + incident.decision.state = "completed" + + # 持久化 + await self._persist_incident(incident) + + logger.info( + "incident_resolved_after_approval", + incident_id=incident_id, + approval_id=approval_id, + old_status=old_status.value, + new_status="resolved", + ) + + return True + + except Exception as e: + logger.exception( + "resolve_incident_error", + incident_id=incident_id, + approval_id=approval_id, + error=str(e), + ) + return False + # ============================================================================= # Singleton