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)