from __future__ import annotations from .shared import * from ...services.sstc.entity_lookup import _ensure_charter, _ensure_project, _ensure_release from ...services.sstc.governance import _assert_release_transition_allowed, _require_manager from ...services.sstc.signal_core import _as_dict, _parse_node_id, _parse_project_id router = APIRouter(tags=["sstc"]) @router.patch("/projects/{project_id}/releases/{release_node_id}/status") async def patch_release_status( project_id: str, release_node_id: str, body: ReleaseStatusPatchRequest, permission_user: User = Depends(require_project_member_or_manager), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): _ = permission_user _require_manager(current_user) pid = _parse_project_id(project_id) rid = _parse_node_id(release_node_id, "release_node_id") tenant_id = int(current_user.tenant_id) await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid) release_node = await _ensure_release( db, tenant_id=tenant_id, project_id=pid, release_node_id=rid ) await _assert_release_transition_allowed( db, tenant_id=tenant_id, project_id=pid, release_node=release_node, target_status=body.status, ) before = {"status": release_node.status} release_node.status = body.status content = _as_dict(release_node.content) content["status_note"] = body.note content["last_status_changed_at"] = now_cn_aware().isoformat() release_node.content = content await log_operation( db, tenant_id=tenant_id, user_id=int(current_user.id), project_id=pid, entity_type="release", entity_id=str(rid), action="status_change", before_data=before, after_data={"status": body.status, "note": body.note}, ) await db.commit() return success_response({"id": str(release_node.id), "status": release_node.status}) @router.post("/projects/{project_id}/releases/{release_node_id}/verification/generate") async def generate_release_verification( project_id: str, release_node_id: str, permission_user: User = Depends(require_project_member_or_manager), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): _ = permission_user _require_manager(current_user) pid = _parse_project_id(project_id) rid = _parse_node_id(release_node_id, "release_node_id") tenant_id = int(current_user.tenant_id) await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid) release_node = await _ensure_release( db, tenant_id=tenant_id, project_id=pid, release_node_id=rid ) content = _as_dict(release_node.content) charter_id = int(content.get("charter_id") or 0) charter = await _ensure_charter( db, tenant_id=tenant_id, project_id=pid, charter_id=charter_id, ) charter_content = _as_dict(charter.content) derived_artifacts = charter_content.get("derived_artifacts") if not isinstance(derived_artifacts, dict): derived_artifacts = {} verification_metrics = derived_artifacts.get("verification_metrics") if not isinstance(verification_metrics, list): verification_metrics = [] if not verification_metrics: fallback_metrics = charter_content.get("success_metrics") or charter_content.get("core_metrics") or [] verification_metrics = [ {"name": str(metric).strip(), "expected": str(metric).strip()} for metric in fallback_metrics if str(metric or "").strip() ] if not verification_metrics: raise HTTPException(status_code=409, detail="charter verification metrics 缺失,请先完善宪章 KPI") items = [ { "id": f"check-{i+1}", "title": str(metric.get("name") or metric.get("title") or "").strip(), "expected": str(metric.get("expected") or metric.get("name") or metric.get("title") or "").strip(), "status": "pending", "required": True, "source": "charter_verification_metric", "verified_by": None, "verified_at": None, "note": None, } for i, metric in enumerate(verification_metrics) if isinstance(metric, dict) and str(metric.get("name") or metric.get("title") or "").strip() ] if not items: raise HTTPException(status_code=409, detail="charter verification metrics 缺失,请先完善宪章 KPI") content["verification_items"] = items content["verification_progress"] = {"done": 0, "total": len(items)} content["verification_done"] = False release_node.content = content await db.commit() return success_response({"release_id": str(release_node.id), "verification_items": items})