from __future__ import annotations from .shared import * from ...services.sstc.entity_lookup import _ensure_project, _ensure_release from ...services.sstc.governance import _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}/verification/items/{item_id}") async def patch_release_verification_item( project_id: str, release_node_id: str, item_id: str, body: ReleaseVerificationItemPatchRequest, 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) items = content.get("verification_items") if not isinstance(items, list) or len(items) == 0: raise HTTPException(status_code=404, detail="验证清单不存在,请先生成") target = None for item in items: if str(item.get("id")) == item_id: target = item break if target is None: raise HTTPException(status_code=404, detail="验证项不存在") target["status"] = body.status if body.status == "done": target["verified_by"] = str(current_user.id) target["verified_at"] = now_cn_aware().isoformat() else: target["verified_by"] = None target["verified_at"] = None target["note"] = body.note done_count = len([item for item in items if str(item.get("status") or "").lower() == "done"]) content["verification_items"] = items content["verification_progress"] = {"done": done_count, "total": len(items)} content["verification_done"] = done_count == len(items) and len(items) > 0 release_node.content = content await db.commit() return success_response( { "release_id": str(release_node.id), "item_id": item_id, "verification_progress": content["verification_progress"], "verification_done": content["verification_done"], } ) @router.post("/projects/{project_id}/releases/{release_node_id}/verification/complete") async def complete_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 ) status_raw = str(release_node.status or "").lower() if status_raw not in {"online", "releasing"}: raise HTTPException(status_code=409, detail="仅上线中的发布计划可完成验证") content = _as_dict(release_node.content) items = content.get("verification_items", []) if not isinstance(items, list) or len(items) == 0: raise HTTPException(status_code=409, detail="验证清单为空,请先生成并完成验证项") incomplete = [item for item in items if str(item.get("status") or "").lower() != "done"] if incomplete: raise HTTPException(status_code=409, detail="仍有未完成验证项,请先逐项完成") now_str = now_cn_aware().isoformat() content["verification_items"] = items content["verification_done"] = True content["verification_progress"] = {"done": len(items), "total": len(items)} content["verified_at"] = now_str release_node.content = content release_node.status = "verified" await db.commit() return success_response({"id": str(release_node.id), "status": release_node.status})