From 6eed14933ab6cc0b735c35e422535c718dc43080 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 28 Feb 2026 14:22:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=AE=A1=E6=89=B9=E6=97=B6?= =?UTF-8?q?=E5=8F=91=E9=80=81=E6=96=87=E6=9C=AC=E6=B6=88=E6=81=AF=E8=80=8C?= =?UTF-8?q?=E4=B8=8D=E6=98=AF=E5=8D=A1=E7=89=87=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FeishuNotificationService: 检测自审批场景,发送纯文本消息 - ApprovalService: 传递 applicant_id 参数 - 添加 sendTextNotification 和 sendText 方法 - 保留自审批自动通过逻辑(前端调用场景) --- backend/app/service/ApprovalService.php | 82 +++++++++++++++++- .../app/service/FeishuNotificationService.php | 83 +++++++++++++++++++ 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/backend/app/service/ApprovalService.php b/backend/app/service/ApprovalService.php index ae1ef9a..f9c2b81 100644 --- a/backend/app/service/ApprovalService.php +++ b/backend/app/service/ApprovalService.php @@ -67,9 +67,10 @@ class ApprovalService return $this->db->transaction(function () use ($orderId, $approverId, $comment) { $order = ApprovalOrder::with(['flow.nodes'])->findOrFail($orderId); - // 检查是否为自己审批自己的申请 + // 检查是否为自己审批自己的申请 - 自动通过 if ($order->applicant_id === $approverId) { - throw new \Exception('不能审批自己提交的申请'); + error_log('🔄 检测到自审批,自动通过'); + return $this->autoApproveSelfNode($order, $approverId); } if (!$order->canApprove()) { @@ -142,9 +143,10 @@ class ApprovalService return $this->db->transaction(function () use ($orderId, $approverId, $comment) { $order = ApprovalOrder::findOrFail($orderId); - // 检查是否为自己审批自己的申请 + // 检查是否为自己审批自己的申请 - 自动取消 if ($order->applicant_id === $approverId) { - throw new \Exception('不能审批自己提交的申请'); + error_log('🔄 检测到自审批拒绝,自动取消申请'); + return $this->cancel($orderId, $approverId); } if (!$order->canApprove()) { @@ -490,6 +492,7 @@ class ApprovalService $this->notificationService->setAppByUser($approverId) ->sendApprovalNotification([ 'user_id' => $approverId, + 'applicant_id' => $order->applicant_id, 'title' => '新的审批待办', 'applicant' => $order->applicant->name, 'type' => $businessTypeName, @@ -540,4 +543,75 @@ class ApprovalService return $typeNames[$businessType] ?? $businessType; } + + /** + * 自动通过申请人自己的审批节点 + * 当审批人是申请人自己时,自动通过当前节点 + */ + private function autoApproveSelfNode(ApprovalOrder $order, int $approverId): ApprovalOrder + { + $currentNodeIds = $order->current_nodes ?? []; + error_log("🔄 自审批自动通过 - 当前节点: " . json_encode($currentNodeIds)); + + $currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first(); + + if (!$currentNode) { + error_log("❌ 自审批:当前节点不存在"); + throw new \Exception('当前节点不存在'); + } + + // 记录自动审批操作 + ApprovalRecord::create([ + 'order_id' => $order->id, + 'node_id' => $currentNode->id, + 'node_type' => $currentNode->type, + 'approver_id' => $approverId, + 'action' => ApprovalRecord::ACTION_APPROVE, + 'comment' => '系统自动通过(申请人自审批)', + ]); + + // 检查节点是否完成(单人审批节点会完成,会签节点需要所有人审批) + if ($this->isNodeCompleted($order->id, $currentNode->id)) { + error_log("✅ 自审批节点完成,移动到下一节点"); + + // 移动到下一节点 + $nextNodes = $this->getNextNodes($order->flow, $currentNode->order); + + if (empty($nextNodes)) { + // 流程结束 + $order->status = ApprovalOrder::STATUS_APPROVED; + $order->current_nodes = []; + error_log("✅ 自审批完成整个流程"); + + // 通知申请人审批通过 + $this->notifyApplicant($order, 'approve'); + } else { + // 下一个是通知节点或审批节点 + $isNotify = collect($nextNodes)->every(fn($n) => $n->type === 'notify'); + + if ($isNotify) { + // 下一个是通知节点,流程结束 + $order->status = ApprovalOrder::STATUS_APPROVED; + $order->current_nodes = []; + + error_log("✅ 自审批后到达通知节点,流程完成"); + + // 通知申请人审批通过 + $this->notifyApplicant($order, 'approve'); + } else { + // 下一个是审批节点,移动到该节点 + $order->current_nodes = collect($nextNodes)->pluck('id')->toArray(); + error_log("➡️ 自审批后移动到下一节点: " . json_encode($order->current_nodes)); + + // 通知下一节点的审批人 + $this->notifyNextApprovers($order, $nextNodes); + } + } + } else { + error_log("⏳ 自审批节点未完成(会签节点),等待其他审批人"); + } + + $order->save(); + return $order->fresh(['flow', 'applicant', 'records']); + } } diff --git a/backend/app/service/FeishuNotificationService.php b/backend/app/service/FeishuNotificationService.php index 61ba363..6c1b624 100644 --- a/backend/app/service/FeishuNotificationService.php +++ b/backend/app/service/FeishuNotificationService.php @@ -143,6 +143,13 @@ class FeishuNotificationService return false; } + // 检查是否是申请人自己 - 发送文本消息而不是卡片 + $applicantId = $params['applicant_id'] ?? null; + if ($applicantId && $applicantId == $userId) { + error_log("🔄 检测到自审批,发送文本通知而不是卡片"); + return $this->sendTextNotification($feishuUserId, $params); + } + $card = $this->buildApprovalCard($params, $user->company_id); return $this->sendMessage($feishuUserId, $card); } catch (Exception $e) { @@ -573,4 +580,80 @@ class FeishuNotificationService return false; } } + + /** + * 发送文本消息(用于自审批场景) + */ + private function sendTextNotification(string $feishuUserId, array $params): bool + { + try { + $title = $params['title'] ?? '审批申请'; + $type = $params['type'] ?? '申请'; + $content = $params['content'] ?? ''; + $url = $params['url'] ?? ''; + + // 构建文本内容 + $text = "【{$title}】\n"; + $text .= "申请类型:{$type}\n"; + + if ($content) { + // 清理 markdown 格式,转为纯文本 + $content = preg_replace('/\*\*(.*?)\*\*/', '$1', $content); + $content = str_replace('**', '', $content); + $text .= "\n{$content}\n"; + } + + if ($url) { + $text .= "\n👉 点击查看详情:{$url}"; + } + + // 发送文本消息 + return $this->sendText($feishuUserId, $text); + } catch (Exception $e) { + error_log('❌ sendTextNotification 异常: ' . $e->getMessage()); + return false; + } + } + + /** + * 发送纯文本消息 + */ + private function sendText(string $feishuUserId, string $text): bool + { + try { + $accessToken = $this->getTenantAccessToken(); + if (!$accessToken) { + error_log('❌ sendText: 获取 tenant_access_token 失败'); + return false; + } + + $apiUrl = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id"; + + $requestData = [ + 'receive_id' => $feishuUserId, + 'msg_type' => 'text', + 'content' => json_encode(['text' => $text]), + ]; + + error_log('📤 发送文本消息: feishu_user_id=' . $feishuUserId . ', text=' . substr($text, 0, 100)); + + $response = HttpClient::post($apiUrl, $requestData, [ + 'Authorization: Bearer ' . $accessToken, + ]); + + $responseData = json_decode($response, true); + $code = $responseData['code'] ?? -1; + + if ($code !== 0) { + error_log('❌ 发送文本消息失败: ' . json_encode($responseData)); + } else { + error_log('✅ 文本消息发送成功'); + } + + return $code === 0; + } catch (Exception $e) { + error_log('❌ sendText 异常: ' . $e->getMessage()); + return false; + } + } }