192 lines
6.8 KiB
Python
192 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
def _queue_resume_work_item(
|
|
*,
|
|
tenant_id: int,
|
|
work_item_id: int,
|
|
source_message_id: Optional[str],
|
|
sender: str,
|
|
project_id: int,
|
|
) -> None:
|
|
message_id = (
|
|
str(source_message_id or "").strip()
|
|
or f"decision-approve-bg-{int(work_item_id)}-{int(datetime.now().timestamp() * 1000)}"
|
|
)[:128]
|
|
task = asyncio.create_task(
|
|
_run_resume_work_item_in_background(
|
|
tenant_id=int(tenant_id),
|
|
work_item_id=int(work_item_id),
|
|
source_message_id=message_id,
|
|
sender=str(sender or "PMFlow用户"),
|
|
project_id=int(project_id),
|
|
),
|
|
name=f"ai-pmo-resume-{int(work_item_id)}",
|
|
)
|
|
|
|
def _on_done(done_task: asyncio.Task[None]) -> None:
|
|
if done_task.cancelled():
|
|
return
|
|
exc = done_task.exception()
|
|
if exc is not None:
|
|
logger.warning(
|
|
"ai_pmo_background_resume_failed",
|
|
tenant_id=int(tenant_id),
|
|
work_item_id=int(work_item_id),
|
|
error=str(exc)[:400],
|
|
)
|
|
|
|
task.add_done_callback(_on_done)
|
|
|
|
async def _run_resume_work_item_in_background(
|
|
*,
|
|
tenant_id: int,
|
|
work_item_id: int,
|
|
source_message_id: str,
|
|
sender: str,
|
|
project_id: int,
|
|
) -> None:
|
|
async with AsyncSessionLocal() as bg_db:
|
|
service = AIPMOKernelService(bg_db, tenant_id=int(tenant_id))
|
|
await service.process_ingress(
|
|
source_system="ai_pmo_ui",
|
|
source_message_id=source_message_id,
|
|
message_text=f"继续处理 work_item #{int(work_item_id)}",
|
|
sender=str(sender or "PMFlow用户"),
|
|
channel_ref="ai_pmo_ui",
|
|
source_sender_open_id=None,
|
|
captured_at=None,
|
|
chat_type="manual",
|
|
project_hint_id=int(project_id),
|
|
)
|
|
|
|
def _normalize_resume_result(*, raw: dict[str, Any], target_work_item: WorkItem) -> dict[str, Any]:
|
|
if not bool(raw.get("handled")):
|
|
reason = str(raw.get("error") or raw.get("reason") or "继续执行失败").strip() or "继续执行失败"
|
|
return {
|
|
"handled": False,
|
|
"status": "failed_safe",
|
|
"followup_reply": reason,
|
|
"target_work_item_id": int(target_work_item.id),
|
|
"target_state": str(target_work_item.state or "").strip() or None,
|
|
"target_intent": str(target_work_item.intent or "").strip() or None,
|
|
"project_id": _safe_int(target_work_item.project_id),
|
|
"signal_id": _safe_int(target_work_item.signal_id),
|
|
"auto_tasks": [],
|
|
}
|
|
|
|
state = None
|
|
rows = raw.get("results") if isinstance(raw.get("results"), list) else []
|
|
if rows and isinstance(rows[0], dict):
|
|
state = str(rows[0].get("state") or "").strip() or None
|
|
if not state:
|
|
state = str(target_work_item.state or "").strip() or None
|
|
|
|
if state == "succeeded":
|
|
status = "succeeded"
|
|
elif state == "waiting_manual":
|
|
status = "pending_manual"
|
|
elif state == "ignored":
|
|
status = "invalidated"
|
|
else:
|
|
status = "failed_safe"
|
|
|
|
followup_reply = str(raw.get("direct_reply_text") or "").strip()
|
|
if not followup_reply:
|
|
if status == "succeeded":
|
|
followup_reply = "已继续执行并完成。"
|
|
elif status == "pending_manual":
|
|
followup_reply = "继续执行后仍需补充信息或人工确认。"
|
|
elif status == "invalidated":
|
|
followup_reply = "该工作项已失效,不再继续执行。"
|
|
else:
|
|
followup_reply = "继续执行失败,请查看详情。"
|
|
|
|
linked_project_ids = raw.get("linked_project_ids") if isinstance(raw.get("linked_project_ids"), list) else []
|
|
project_id = _safe_int(linked_project_ids[0]) if linked_project_ids else _safe_int(target_work_item.project_id)
|
|
return {
|
|
"handled": True,
|
|
"status": status,
|
|
"followup_reply": followup_reply,
|
|
"target_work_item_id": int(target_work_item.id),
|
|
"target_state": state,
|
|
"target_intent": str(target_work_item.intent or "").strip() or None,
|
|
"project_id": project_id,
|
|
"signal_id": _safe_int(raw.get("signal_id")) or _safe_int(target_work_item.signal_id),
|
|
"auto_tasks": raw.get("auto_tasks") if isinstance(raw.get("auto_tasks"), list) else [],
|
|
}
|
|
|
|
async def _reject_work_item(
|
|
*,
|
|
db: AsyncSession,
|
|
tenant_id: int,
|
|
target: WorkItem,
|
|
current_user: User,
|
|
reason: Optional[str],
|
|
) -> dict[str, Any]:
|
|
current_state = str(target.state or "").strip()
|
|
if current_state == "ignored":
|
|
return {
|
|
"handled": True,
|
|
"status": "invalidated",
|
|
"followup_reply": "该工作项已处于失效状态。",
|
|
"target_work_item_id": int(target.id),
|
|
"target_state": "ignored",
|
|
"target_intent": str(target.intent or "").strip() or None,
|
|
"project_id": _safe_int(target.project_id),
|
|
"signal_id": _safe_int(target.signal_id),
|
|
"auto_tasks": [],
|
|
}
|
|
|
|
reject_reason = str(reason or "").strip() or "人工审批拒绝,工作项已失效。"
|
|
existing_execution = target.execution_result if isinstance(target.execution_result, dict) else {}
|
|
target.state = "ignored"
|
|
target.execution_result = {
|
|
**existing_execution,
|
|
"status": "invalidated",
|
|
"manual_decision": "reject",
|
|
"manual_reason": reject_reason,
|
|
"operator_user_id": _safe_int(current_user.id),
|
|
"processed_at": datetime.now().isoformat(),
|
|
}
|
|
target.review_summary = {
|
|
"verified": False,
|
|
"issues": ["rejected_by_user"],
|
|
"next_step": "该工作项已进入失效状态",
|
|
"operator": str(current_user.name or current_user.username or "").strip() or None,
|
|
"reason": reject_reason,
|
|
}
|
|
target.reply_template_key = "action_rejected"
|
|
|
|
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:
|
|
decision_row.execution_result = target.execution_result
|
|
decision_row.final_outcome = "ignored"
|
|
|
|
await db.commit()
|
|
return {
|
|
"handled": True,
|
|
"status": "invalidated",
|
|
"followup_reply": reject_reason,
|
|
"target_work_item_id": int(target.id),
|
|
"target_state": "ignored",
|
|
"target_intent": str(target.intent or "").strip() or None,
|
|
"project_id": _safe_int(target.project_id),
|
|
"signal_id": _safe_int(target.signal_id),
|
|
"auto_tasks": [],
|
|
}
|
|
|