from __future__ import annotations import logging from typing import TYPE_CHECKING, Any import httpx from ._validate import validate_params if TYPE_CHECKING: from yuxi.channels.adapters.dingding.token import DingDingTokenManager logger = logging.getLogger(__name__) DINGTALK_API_BASE = "https://api.dingtalk.com" @validate_params async def list_approval_templates( token_manager: DingDingTokenManager, *, user_id: str = "", page_size: int = 50, page_number: int = 1, http_client: httpx.AsyncClient | None = None, ) -> dict[str, Any]: token = await token_manager.get_token() url = f"{DINGTALK_API_BASE}/v1.0/workflow/processes" headers = {"x-acs-dingtalk-access-token": token} params: dict[str, Any] = { "maxResults": min(page_size, 100), "nextToken": str(page_number - 1), } if user_id: params["userId"] = user_id try: if http_client is not None: resp = await http_client.get(url, headers=headers, params=params) else: async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client: resp = await client.get(url, headers=headers, params=params) if resp.status_code != 200: raise RuntimeError(f"获取审批模板列表失败: HTTP {resp.status_code}: {resp.text[:200]}") data = resp.json() items = data.get("items", data.get("processList", data.get("list", []))) templates = [ { "process_code": item.get("processCode", item.get("code", "")), "name": item.get("name", item.get("processName", "")), "description": item.get("description", ""), "icon_url": item.get("iconUrl", ""), "status": item.get("status", ""), } for item in items ] return { "templates": templates, "total": data.get("totalCount", len(templates)), "has_more": data.get("hasMore", False), } except Exception as e: logger.error(f"[DingDing Approval] list_approval_templates failed: {e}") raise RuntimeError(f"获取审批模板列表失败: {e}") from e @validate_params async def create_approval_instance( token_manager: DingDingTokenManager, process_code: str, originator_user_id: str, form_component_values: list[dict[str, Any]], *, dept_id: str = "", approvers: list[str] | None = None, cc_list: list[str] | None = None, http_client: httpx.AsyncClient | None = None, ) -> dict[str, Any]: token = await token_manager.get_token() url = f"{DINGTALK_API_BASE}/v1.0/workflow/processInstances" headers = { "x-acs-dingtalk-access-token": token, "Content-Type": "application/json", } payload: dict[str, Any] = { "processCode": process_code, "originatorUserId": originator_user_id, "formComponentValues": form_component_values, } if dept_id: payload["deptId"] = dept_id if approvers: payload["approvers"] = approvers if cc_list: payload["ccList"] = cc_list try: if http_client is not None: resp = await http_client.post(url, json=payload, headers=headers) else: async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client: resp = await client.post(url, json=payload, headers=headers) if resp.status_code != 200: raise RuntimeError(f"发起审批实例失败: HTTP {resp.status_code}: {resp.text[:200]}") data = resp.json() return { "instance_id": data.get("instanceId", data.get("processInstanceId", "")), "process_code": process_code, "originator_user_id": originator_user_id, "status": data.get("status", "RUNNING"), } except Exception as e: logger.error(f"[DingDing Approval] create_approval_instance failed: {e}") raise RuntimeError(f"发起审批实例失败: {e}") from e @validate_params async def get_approval_instance( token_manager: DingDingTokenManager, instance_id: str, *, http_client: httpx.AsyncClient | None = None, ) -> dict[str, Any]: token = await token_manager.get_token() url = f"{DINGTALK_API_BASE}/v1.0/workflow/processInstances/{instance_id}" headers = {"x-acs-dingtalk-access-token": token} try: if http_client is not None: resp = await http_client.get(url, headers=headers) else: async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client: resp = await client.get(url, headers=headers) if resp.status_code != 200: raise RuntimeError(f"获取审批实例详情失败: HTTP {resp.status_code}: {resp.text[:200]}") data = resp.json() return { "instance_id": data.get("instanceId", data.get("processInstanceId", instance_id)), "process_code": data.get("processCode", ""), "title": data.get("title", data.get("processTitle", "")), "originator_user_id": data.get("originatorUserId", ""), "status": data.get("status", ""), "result": data.get("result", ""), "form_values": data.get("formComponentValues", []), "create_time": data.get("createTime", ""), "finish_time": data.get("finishTime", ""), "approvers": data.get("approvers", []), "tasks": data.get("tasks", data.get("operationRecords", [])), } except Exception as e: logger.error(f"[DingDing Approval] get_approval_instance failed: {e}") raise RuntimeError(f"获取审批实例详情失败: {e}") from e @validate_params async def cancel_approval_instance( token_manager: DingDingTokenManager, instance_id: str, *, operate_user_id: str = "", remark: str = "", http_client: httpx.AsyncClient | None = None, ) -> dict[str, Any]: token = await token_manager.get_token() url = f"{DINGTALK_API_BASE}/v1.0/workflow/processInstances/{instance_id}/terminate" headers = { "x-acs-dingtalk-access-token": token, "Content-Type": "application/json", } payload: dict[str, Any] = {} if operate_user_id: payload["operateUserId"] = operate_user_id if remark: payload["remark"] = remark try: if http_client is not None: resp = await http_client.put(url, json=payload, headers=headers) else: async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client: resp = await client.put(url, json=payload, headers=headers) if resp.status_code != 200: raise RuntimeError(f"撤销审批实例失败: HTTP {resp.status_code}: {resp.text[:200]}") data = resp.json() return { "instance_id": instance_id, "canceled": True, "result": data.get("result", True), } except Exception as e: logger.error(f"[DingDing Approval] cancel_approval_instance failed: {e}") raise RuntimeError(f"撤销审批实例失败: {e}") from e