216 lines
7.1 KiB
Python
216 lines
7.1 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
from ...services.sstc.entity_lookup import _ensure_charter, _ensure_project, _ensure_release, _ensure_review, _parse_due_date
|
|
from ...services.sstc.governance import _assert_charter_aligned, _require_manager
|
|
from ...services.sstc.review_utils import _normalize_review_content
|
|
from ...services.sstc.signal_core import _as_dict, _parse_node_id, _parse_project_id, _sync_pmflow_rag_async
|
|
|
|
router = APIRouter(tags=["sstc"])
|
|
|
|
@router.patch("/projects/{project_id}/reviews/{review_node_id}/content")
|
|
async def patch_review_content(
|
|
project_id: str,
|
|
review_node_id: str,
|
|
body: ReviewContentPatchRequest,
|
|
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(review_node_id, "review_node_id")
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
|
|
review_node = await _ensure_review(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
project_id=pid,
|
|
review_node_id=rid,
|
|
)
|
|
|
|
existing = _as_dict(review_node.content)
|
|
merged = dict(existing)
|
|
merged.update(body.content or {})
|
|
normalized = _normalize_review_content(merged)
|
|
normalized["release_node_id"] = existing.get("release_node_id")
|
|
normalized["charter_id"] = existing.get("charter_id")
|
|
review_node.content = normalized
|
|
|
|
await log_operation(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
user_id=int(current_user.id),
|
|
project_id=pid,
|
|
entity_type="review",
|
|
entity_id=str(rid),
|
|
action="update_content",
|
|
after_data={
|
|
"improvement_count": len(normalized.get("improvements") or []),
|
|
"summary_len": len(str(normalized.get("summary") or "")),
|
|
},
|
|
)
|
|
await db.commit()
|
|
_sync_pmflow_rag_async(
|
|
tenant_id=tenant_id,
|
|
record_type="review",
|
|
record_id=int(review_node.id),
|
|
project_id=pid,
|
|
title=review_node.title,
|
|
content=_as_dict(review_node.content),
|
|
)
|
|
return success_response(
|
|
{
|
|
"id": str(review_node.id),
|
|
"status": review_node.status,
|
|
"content": _as_dict(review_node.content),
|
|
}
|
|
)
|
|
|
|
@router.post("/projects/{project_id}/reviews/{review_node_id}/improvements/convert-task")
|
|
async def convert_review_improvement_to_task(
|
|
project_id: str,
|
|
review_node_id: str,
|
|
body: ReviewImprovementToTaskRequest,
|
|
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(review_node_id, "review_node_id")
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
|
|
review_node = await _ensure_review(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
project_id=pid,
|
|
review_node_id=rid,
|
|
)
|
|
review_content = _normalize_review_content(_as_dict(review_node.content))
|
|
improvements = review_content.get("improvements") or []
|
|
if body.improvement_index >= len(improvements):
|
|
raise HTTPException(status_code=400, detail="改进项索引超出范围")
|
|
item = improvements[body.improvement_index]
|
|
if not isinstance(item, dict):
|
|
raise HTTPException(status_code=400, detail="改进项数据无效")
|
|
|
|
release_node_id = int(review_node.parent_id or review_content.get("release_node_id") or 0)
|
|
if release_node_id <= 0:
|
|
raise HTTPException(status_code=409, detail="复盘未关联发布计划,无法生成任务")
|
|
release_node = await _ensure_release(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
project_id=pid,
|
|
release_node_id=release_node_id,
|
|
)
|
|
|
|
release_content = _as_dict(release_node.content)
|
|
charter_id = (
|
|
int(body.charter_id)
|
|
if body.charter_id
|
|
else int(release_content.get("charter_id") or release_node.parent_id or 0)
|
|
)
|
|
if charter_id <= 0:
|
|
raise HTTPException(status_code=409, detail="无法定位宪章,改进项无法转任务")
|
|
charter = await _ensure_charter(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
project_id=pid,
|
|
charter_id=charter_id,
|
|
)
|
|
_assert_charter_aligned(charter)
|
|
|
|
title = compact_title(
|
|
body.title or str(item.get("title") or "").strip() or "复盘改进项任务",
|
|
max_len=32,
|
|
)
|
|
if not title:
|
|
raise HTTPException(status_code=400, detail="改进项标题为空")
|
|
desc_parts = [
|
|
str(body.description or "").strip(),
|
|
str(item.get("description") or "").strip(),
|
|
f"来源复盘: {review_node.title}",
|
|
]
|
|
description = "\n".join([part for part in desc_parts if part])
|
|
due_date = _parse_due_date(body.due_date)
|
|
|
|
task = await persist_task(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
truth_node_id=charter_id,
|
|
project_id=pid,
|
|
draft=build_task_create_draft(
|
|
title=title,
|
|
description=description or None,
|
|
assignee_id=item.get("owner_id"),
|
|
required_skill_tag_ids=[],
|
|
status="todo",
|
|
priority=body.priority,
|
|
estimated_hours=body.estimated_hours,
|
|
due_date=due_date,
|
|
),
|
|
actor_user_id=int(current_user.id),
|
|
action="create_from_review_improvement",
|
|
remark="sstc.review_improvement_to_task",
|
|
commit=False,
|
|
refresh_workload=False,
|
|
)
|
|
|
|
item["task_id"] = int(task.id)
|
|
item["status"] = "converted"
|
|
review_content["improvements"] = improvements
|
|
review_content["release_node_id"] = release_node_id
|
|
review_content["charter_id"] = charter_id
|
|
review_node.content = review_content
|
|
|
|
await log_operation(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
user_id=int(current_user.id),
|
|
project_id=pid,
|
|
entity_type="task",
|
|
entity_id=str(task.id),
|
|
action="create_from_review_improvement",
|
|
after_data={
|
|
"review_id": rid,
|
|
"improvement_index": body.improvement_index,
|
|
"charter_id": charter_id,
|
|
},
|
|
)
|
|
await db.commit()
|
|
await db.refresh(task)
|
|
_sync_pmflow_rag_async(
|
|
tenant_id=tenant_id,
|
|
record_type="task",
|
|
record_id=int(task.id),
|
|
project_id=pid,
|
|
title=task.title,
|
|
content=task.description or "",
|
|
)
|
|
_sync_pmflow_rag_async(
|
|
tenant_id=tenant_id,
|
|
record_type="review",
|
|
record_id=int(review_node.id),
|
|
project_id=pid,
|
|
title=review_node.title,
|
|
content=_as_dict(review_node.content),
|
|
)
|
|
return success_response(
|
|
{
|
|
"task": {
|
|
"id": str(task.id),
|
|
"title": task.title,
|
|
"status": task.status,
|
|
"priority": task.priority,
|
|
"charter_id": str(charter_id),
|
|
},
|
|
"review_id": str(review_node.id),
|
|
"improvement_index": body.improvement_index,
|
|
}
|
|
)
|
|
|