PMFlow/backend/app/api/ai_pmo_api/routes_resume.py

188 lines
7.4 KiB
Python

from __future__ import annotations
from .shared import *
from .helpers_resume import _queue_resume_work_item, _reject_work_item
from .schemas import PMODraftDecisionRequest, PMOResumeWorkItemRequest, PMOWorkItemDecisionRequest
from ...services.ai_pmo.draft_confirm_handler import DraftConfirmHandler
@router.post("/work-items/{work_item_id}/resume")
async def resume_work_item(
work_item_id: int,
request: PMOResumeWorkItemRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
target = (
await db.execute(
select(WorkItem).where(
and_(
WorkItem.tenant_id == tenant_id,
WorkItem.id == int(work_item_id),
)
).limit(1)
)
).scalar_one_or_none()
if target is None:
raise HTTPException(status_code=404, detail="work_item_not_found")
action_text_map = {
"continue": "继续处理",
"commit": "确认并提交候选",
"refresh": "换一批候选",
"cancel": "取消本次候选",
}
action_message = action_text_map.get(str(request.action or "").strip().lower(), "继续处理")
service = AIPMOKernelService(db, tenant_id=tenant_id)
raw_result = await service.process_ingress(
source_system="ai_pmo_ui",
source_message_id=request.source_message_id or f"resume-{int(work_item_id)}-{int(datetime.now().timestamp() * 1000)}",
message_text=f"{action_message} work_item #{int(work_item_id)}",
sender=str(current_user.name or current_user.username or "PMFlow用户"),
channel_ref="ai_pmo_ui",
source_sender_open_id=None,
captured_at=None,
chat_type="manual",
project_hint_id=request.project_id or _safe_int(target.project_id),
)
result = _normalize_resume_result(raw=raw_result, target_work_item=target)
return success_response(result)
@router.post("/work-items/{work_item_id}/decision")
async def decide_work_item(
work_item_id: int,
request: PMOWorkItemDecisionRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
target = (
await db.execute(
select(WorkItem).where(
and_(
WorkItem.tenant_id == tenant_id,
WorkItem.id == int(work_item_id),
)
).limit(1)
)
).scalar_one_or_none()
if target is None:
raise HTTPException(status_code=404, detail="work_item_not_found")
if request.action == "reject":
result = await _reject_work_item(
db=db,
tenant_id=tenant_id,
target=target,
current_user=current_user,
reason=request.reason,
)
return success_response(result)
if str(target.state or "").strip() == "waiting_manual" and request.project_id is not None:
target.project_id = int(request.project_id)
target.state = "executing"
target.reply_template_key = "action_processing"
current_execution = target.execution_result if isinstance(target.execution_result, dict) else {}
target.execution_result = {
**current_execution,
"status": "executing",
"manual_decision": "approve",
"resume_trigger": "queued_background",
"project_id": int(request.project_id),
"queued_at": datetime.now().isoformat(),
}
decision_row = (
await db.execute(
select(DecisionRecord)
.where(
and_(
DecisionRecord.tenant_id == tenant_id,
DecisionRecord.work_item_id == int(target.id),
)
)
.order_by(DecisionRecord.id.desc())
.limit(1)
)
).scalar_one_or_none()
if decision_row is not None and str(decision_row.policy_gate_code or "").strip() == "missing_project_scope":
decision_row.policy_gate_hit = False
decision_row.policy_gate_code = None
decision_row.approval_requirement = {
"required": False,
"reason": "project_scope_confirmed",
"message": "项目归属已确认,正在后台继续执行。",
}
decision_row.final_outcome = "executing"
await db.commit()
_queue_resume_work_item(
tenant_id=tenant_id,
work_item_id=int(work_item_id),
source_message_id=request.source_message_id,
sender=str(current_user.name or current_user.username or "PMFlow用户"),
project_id=int(request.project_id),
)
return success_response(
{
"handled": True,
"status": "pending_manual",
"followup_reply": "已确认关联项目,正在后台继续执行。",
"target_work_item_id": int(target.id),
"target_state": str(target.state or "").strip() or "executing",
"target_intent": str(target.intent or "").strip() or None,
"project_id": int(request.project_id),
"signal_id": _safe_int(target.signal_id),
"auto_tasks": [],
}
)
service = AIPMOKernelService(db, tenant_id=tenant_id)
raw_result = await service.process_ingress(
source_system="ai_pmo_ui",
source_message_id=request.source_message_id or f"decision-approve-{int(work_item_id)}-{int(datetime.now().timestamp() * 1000)}",
message_text=f"继续处理 work_item #{int(work_item_id)}",
sender=str(current_user.name or current_user.username or "PMFlow用户"),
channel_ref="ai_pmo_ui",
source_sender_open_id=None,
captured_at=None,
chat_type="manual",
project_hint_id=request.project_id or _safe_int(target.project_id),
)
result = _normalize_resume_result(raw=raw_result, target_work_item=target)
return success_response(result)
@router.post("/work-items/{work_item_id}/draft-decision")
async def decide_draft_work_item(
work_item_id: int,
request: PMODraftDecisionRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
target = (
await db.execute(
select(WorkItem).where(
and_(
WorkItem.tenant_id == tenant_id,
WorkItem.id == int(work_item_id),
)
).limit(1)
)
).scalar_one_or_none()
if target is None:
raise HTTPException(status_code=404, detail="work_item_not_found")
handler = DraftConfirmHandler(AIPMOKernelService(db, tenant_id=tenant_id))
result = await handler.handle_api_decision(
work_item_id=int(work_item_id),
action=str(request.action or "").strip().lower(),
operator_sender=str(current_user.name or current_user.username or "PMFlow用户"),
source_message_id=request.source_message_id,
revision_note=request.revision_note,
draft_version=request.draft_version,
)
await db.commit()
if not bool(result.get("handled")):
raise HTTPException(status_code=400, detail=str(result.get("followup_reply") or "draft_decision_failed"))
return success_response(result)