db = Db::connection(); } /** * 提交审批 */ public function submit(array $data): ApprovalOrder { return $this->db->transaction(function () use ($data) { // 1. 获取审批流程 $flow = ApprovalFlow::active() ->byBusinessType($data['business_type']) ->with('nodes') ->findOrFail($data['flow_id']); // 2. 创建审批单 $order = ApprovalOrder::create([ 'business_type' => $data['business_type'], 'business_id' => $data['business_id'], 'title' => $data['title'], 'applicant_id' => $data['applicant_id'], 'status' => ApprovalOrder::STATUS_PENDING, 'current_nodes' => $this->getFirstNodeIds($flow), 'flow_id' => $flow->id, 'form_data' => $data['form_data'] ?? [], 'attachments' => $data['attachments'] ?? [], ]); // 3. 记录提交操作 ApprovalRecord::create([ 'order_id' => $order->id, 'node_id' => $flow->nodes->first()->id ?? 0, 'node_type' => 'submit', 'approver_id' => $data['applicant_id'], 'action' => ApprovalRecord::ACTION_SUBMIT, 'comment' => $data['comment'] ?? null, ]); return $order->load(['flow', 'applicant']); }); } /** * 审批通过 */ public function approve(int $orderId, int $approverId, ?string $comment = null): ApprovalOrder { return $this->db->transaction(function () use ($orderId, $approverId, $comment) { $order = ApprovalOrder::with(['flow.nodes'])->findOrFail($orderId); // 检查是否为自己审批自己的申请 - 自动通过 if ($order->applicant_id === $approverId) { error_log('🔄 检测到自审批,自动通过'); return $this->autoApproveSelfNode($order, $approverId); } if (!$order->canApprove()) { throw new \Exception('当前状态无法审批'); } $currentNodeIds = $order->current_nodes ?? []; error_log("🔍 当前节点 IDs: " . json_encode($currentNodeIds)); $currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first(); if (!$currentNode) { error_log("❌ 未找到节点,IDs: " . json_encode($currentNodeIds)); throw new \Exception('当前节点不存在'); } // 检查当前用户是否已经在当前节点审批过 $existingRecord = ApprovalRecord::where('order_id', $order->id) ->where('node_id', $currentNode->id) ->where('approver_id', $approverId) ->whereIn('action', [ApprovalRecord::ACTION_APPROVE, ApprovalRecord::ACTION_REJECT]) ->first(); if ($existingRecord) { $actionText = $existingRecord->action === ApprovalRecord::ACTION_APPROVE ? '通过' : '拒绝'; throw new \Exception("您已经{$actionText}过此申请,无需重复操作"); } // 记录审批操作 ApprovalRecord::create([ 'order_id' => $order->id, 'node_id' => $currentNode->id, 'node_type' => $currentNode->type, 'approver_id' => $approverId, 'action' => ApprovalRecord::ACTION_APPROVE, 'comment' => $comment, ]); // 判断节点是否完成 if ($this->isNodeCompleted($order->id, $currentNode->id)) { // 对于或签和单人审批,更新其他审批人的消息卡片 if ($currentNode->type === ApprovalNode::TYPE_OR_APPROVE || $currentNode->type === 'approver') { $approver = User::find($approverId); $approverName = $approver ? $approver->name : '未知'; if ($this->notificationService) { $this->notificationService->updateMessageCards( $order->id, $currentNode->id, $approverId, 'approved', $approverName ); } } // 移动到下一节点 $nextNodes = $this->getNextNodes($order->flow, $currentNode->order); if (empty($nextNodes)) { // 没有下一节点,审批完成 $order->status = ApprovalOrder::STATUS_APPROVED; $order->current_nodes = []; // 通知申请人审批通过 $this->notifyApplicant($order, 'approve'); } else { // 检查第一个下一节点 $firstNextNode = $nextNodes[0] ?? null; if ($firstNextNode && $firstNextNode['type'] === 'notify') { // 下一个是抄送节点,跳过并完成流程 $order->status = ApprovalOrder::STATUS_APPROVED; $order->current_nodes = []; // 通知申请人审批通过 $this->notifyApplicant($order, 'approve'); } else { // 下一个是审批节点,移动到该节点 $order->current_nodes = collect($nextNodes)->pluck('id')->toArray(); // 通知下一节点的审批人 $this->notifyNextApprovers($order, $nextNodes); } } $order->save(); } return $order->fresh(['flow', 'applicant', 'records']); }); } /** * 审批拒绝 */ public function reject(int $orderId, int $approverId, ?string $comment = null): ApprovalOrder { return $this->db->transaction(function () use ($orderId, $approverId, $comment) { $order = ApprovalOrder::findOrFail($orderId); // 检查是否为自己审批自己的申请 - 自动取消 if ($order->applicant_id === $approverId) { error_log('🔄 检测到自审批拒绝,自动取消申请'); return $this->cancel($orderId, $approverId); } if (!$order->canApprove()) { throw new \Exception('当前状态无法审批'); } $currentNodeIds = $order->current_nodes ?? []; $currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first(); // 记录拒绝操作 ApprovalRecord::create([ 'order_id' => $order->id, 'node_id' => $currentNode->id ?? 0, 'node_type' => $currentNode->type ?? 'unknown', 'approver_id' => $approverId, 'action' => ApprovalRecord::ACTION_REJECT, 'comment' => $comment, ]); // 更新其他审批人的消息卡片(拒绝时所有人都需要更新) $approver = User::find($approverId); $approverName = $approver ? $approver->name : '未知'; if ($this->notificationService && $currentNode) { $this->notificationService->updateMessageCards( $order->id, $currentNode->id, $approverId, 'rejected', $approverName ); } // 更新状态为已拒绝 $order->status = ApprovalOrder::STATUS_REJECTED; $order->current_nodes = []; $order->save(); // 通知申请人审批被拒绝 $this->notifyApplicant($order, 'reject', $comment); return $order->fresh(['flow', 'applicant', 'records']); }); } /** * 撤销申请 */ public function cancel(int $orderId, int $userId): ApprovalOrder { $order = ApprovalOrder::findOrFail($orderId); if ($order->applicant_id !== $userId) { throw new \Exception('只有申请人可以撤销'); } if (!$order->canEdit() && !$order->canApprove()) { throw new \Exception('当前状态无法撤销'); } // 记录撤销操作 ApprovalRecord::create([ 'order_id' => $order->id, 'node_id' => 0, 'node_type' => 'cancel', 'approver_id' => $userId, 'action' => ApprovalRecord::ACTION_CANCEL, 'comment' => '申请人撤销', ]); $order->status = ApprovalOrder::STATUS_CANCELLED; $order->current_nodes = []; $order->save(); return $order->fresh(['flow', 'applicant', 'records']); } /** * 获取审批详情(包含实际审批人信息) */ public function getDetail(int $orderId): array { $order = ApprovalOrder::with(['flow.nodes', 'applicant', 'records.approver']) ->findOrFail($orderId); // 获取 form_data 中的实际审批人和抄送人 $formData = $order->form_data ?? []; $actualApprovers = $formData['approvers'] ?? []; $actualCcUsers = $formData['cc_users'] ?? []; // 兼容两种格式:对象数组 [{id:1, name:'xxx'}] 或纯ID数组 [1, 2, 3] if (!empty($actualApprovers) && isset($actualApprovers[0]['id'])) { $actualApproverIds = array_column($actualApprovers, 'id'); } else { $actualApproverIds = $actualApprovers; } if (!empty($actualCcUsers) && isset($actualCcUsers[0]['id'])) { $actualCcUserIds = array_column($actualCcUsers, 'id'); } else { $actualCcUserIds = $actualCcUsers; } // 获取实际审批人的完整用户信息 $actualApproverUsers = []; if (!empty($actualApproverIds)) { $actualApproverUsers = User::whereIn('id', $actualApproverIds) ->get() ->keyBy('id') ->toArray(); } // 获取实际抄送人的完整用户信息 $actualCcUserObjects = []; if (!empty($actualCcUserIds)) { $actualCcUserObjects = User::whereIn('id', $actualCcUserIds) ->get() ->keyBy('id') ->toArray(); } // 处理节点信息,添加实际审批人 $nodes = []; if ($order->flow && $order->flow->nodes) { foreach ($order->flow->nodes as $node) { $nodeArray = $node->toArray(); // 为审批节点添加实际审批人信息 if (in_array($node->type, ['approver', 'and_approve', 'or_approve'])) { $nodeArray['actual_approvers'] = []; foreach ($actualApproverIds as $approverId) { if (isset($actualApproverUsers[$approverId])) { $nodeArray['actual_approvers'][] = $actualApproverUsers[$approverId]; } } // 获取每个实际审批人的审批状态 $nodeArray['approver_statuses'] = []; foreach ($actualApproverIds as $approverId) { $record = ApprovalRecord::where('order_id', $orderId) ->where('node_id', $node->id) ->where('approver_id', $approverId) ->whereIn('action', ['approve', 'reject']) ->first(); $nodeArray['approver_statuses'][] = [ 'user_id' => $approverId, 'user' => $actualApproverUsers[$approverId] ?? null, 'action' => $record->action ?? null, 'action_name' => $record ? ($record->action === 'approve' ? '已通过' : '已拒绝') : '待审批', 'comment' => $record->comment ?? null, 'created_at' => $record->created_at ?? null, ]; } } // 为通知节点添加实际抄送人 if ($node->type === 'notify') { $nodeArray['actual_cc_users'] = []; foreach ($actualCcUserIds as $ccUserId) { if (isset($actualCcUserObjects[$ccUserId])) { $nodeArray['actual_cc_users'][] = $actualCcUserObjects[$ccUserId]; } } } $nodes[] = $nodeArray; } } // 构建返回数据 $result = $order->toArray(); $result['flow']['nodes'] = $nodes; $result['actual_approvers'] = array_values($actualApproverUsers); $result['actual_cc_users'] = array_values($actualCcUserObjects); return $result; } /** * 获取审批列表 */ public function getList(int $userId, string $type = 'my', int $page = 1, int $pageSize = 20): array { $query = ApprovalOrder::with(['flow', 'applicant']); switch ($type) { case 'my': $query->where('applicant_id', $userId); error_log("🔍 Debug 'my' query: userId={$userId}"); break; case 'pending': // 获取用户待审批的订单 $pendingOrderIds = $this->getPendingOrderIds($userId); error_log("🔍 Debug 'pending' query: userId={$userId}, pendingOrderIds=" . json_encode($pendingOrderIds)); $query->whereIn('id', $pendingOrderIds); break; case 'approved': // 获取用户已审批的订单(只显示已完成的:approved、rejected、cancelled) $approvedOrderIds = ApprovalRecord::where('approver_id', $userId) ->where('action', '!=', ApprovalRecord::ACTION_SUBMIT) ->whereIn('action', [ApprovalRecord::ACTION_APPROVE, ApprovalRecord::ACTION_REJECT]) ->pluck('order_id') ->unique() ->toArray(); error_log("🔍 Debug 'approved' query: userId={$userId}, approvedOrderIds=" . json_encode($approvedOrderIds)); $query->whereIn('id', $approvedOrderIds) ->whereIn('status', [ApprovalOrder::STATUS_APPROVED, ApprovalOrder::STATUS_REJECTED, ApprovalOrder::STATUS_CANCELLED]); break; case 'cc': // 获取抄送给我的订单 $allOrders = ApprovalOrder::where('status', '!=', ApprovalOrder::STATUS_DRAFT)->get(); $ccOrderIds = []; foreach ($allOrders as $order) { $formData = $order->form_data ?? []; $ccUsers = $formData['cc_users'] ?? []; // 兼容两种格式:[{id:29, name:'...'}] 或 [29] 或 [{id:29}] if (!empty($ccUsers)) { if (is_array($ccUsers[0] ?? null) && isset($ccUsers[0]['id'])) { // 对象数组格式: [{id:29, name:'...'}] $ccUserIds = array_column($ccUsers, 'id'); } else if (is_numeric($ccUsers[0] ?? null)) { // ID数组格式: [29, 30] $ccUserIds = $ccUsers; } else { $ccUserIds = []; } if (in_array($userId, $ccUserIds)) { $ccOrderIds[] = $order->id; } } } error_log("🔍 Debug 'cc' query: userId={$userId}, ccOrderIds=" . json_encode($ccOrderIds)); $query->whereIn('id', $ccOrderIds); break; } $total = $query->count(); error_log("🔍 Debug total count: type={$type}, total={$total}"); $list = $query->orderBy('created_at', 'desc') ->offset(($page - 1) * $pageSize) ->limit($pageSize) ->get(); error_log("🔍 Debug list count: type={$type}, page={$page}, count=" . $list->count()); // 转换为数组避免序列化问题 $listArray = $list->map(function ($item) { return [ 'id' => $item->id, 'title' => $item->title, 'business_type' => $item->business_type, 'status' => $item->status, 'created_at' => $item->created_at ? $item->created_at->toDateTimeString() : null, 'applicant' => $item->applicant ? [ 'id' => $item->applicant->id, 'name' => $item->applicant->name, ] : null, 'flow' => $item->flow ? [ 'id' => $item->flow->id, 'name' => $item->flow->name, ] : null, ]; })->toArray(); return [ 'list' => $listArray, 'total' => $total, 'page' => $page, 'page_size' => $pageSize, ]; } /** * 获取第一个节点ID列表 */ private function getFirstNodeIds(ApprovalFlow $flow): array { return $flow->nodes ->where('order', 0) ->pluck('id') ->toArray(); } /** * 判断节点是否完成 */ private function isNodeCompleted(int $orderId, int $nodeId): bool { $node = ApprovalNode::findOrFail($nodeId); $records = ApprovalRecord::where('order_id', $orderId) ->where('node_id', $nodeId) ->where('action', 'approve') ->get(); // 从 form_data 获取实际审批人列表(用户可能重新选择过) $order = ApprovalOrder::find($orderId); $formData = $order->form_data ?? []; $approvers = $formData['approvers'] ?? []; // 兼容两种格式:[{id:1, name:'...'}] 或 [1] if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) { $approverIds = array_column($approvers, 'id'); } else { $approverIds = $approvers; } if ($node->type === ApprovalNode::TYPE_AND_APPROVE) { // 会签:需要所有人通过 return count($records) >= count($approverIds); } elseif ($node->type === ApprovalNode::TYPE_OR_APPROVE) { // 或签:任一人通过即可 return count($records) >= 1; } else { // 单人审批:需要一人通过 return count($records) >= 1; } } /** * 获取下一节点 */ private function getNextNodes(ApprovalFlow $flow, int $currentOrder): array { return $flow->nodes ->where('order', '>', $currentOrder) ->sortBy('order') ->values() ->toArray(); } /** * 获取用户待审批的订单ID列表 */ private function getPendingOrderIds(int $userId): array { // 获取用户参与的待审批订单 $pendingOrders = ApprovalOrder::where('status', ApprovalOrder::STATUS_PENDING) ->whereNotNull('current_nodes') ->get(); $result = []; foreach ($pendingOrders as $order) { // 从 form_data 获取审批人列表 $formData = $order->form_data ?? []; $approvers = $formData['approvers'] ?? []; // 兼容两种格式:[{id:1, name:'...'}] 或 [1] if (!empty($approvers)) { if (is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) { // 对象数组格式: [{id:1, name:'...'}] $approverIds = array_column($approvers, 'id'); } else { // ID数组格式: [1, 2, 3] $approverIds = $approvers; } if (in_array($userId, $approverIds)) { // 检查是否已审批 $hasApproved = ApprovalRecord::where('order_id', $order->id) ->where('approver_id', $userId) ->whereIn('action', [ApprovalRecord::ACTION_APPROVE, ApprovalRecord::ACTION_REJECT]) ->exists(); if (!$hasApproved) { $result[] = $order->id; } } } } return $result; } /** * 通知申请人 */ private function notifyApplicant(ApprovalOrder $order, string $action, ?string $comment = null): void { try { if (!$this->notificationService) { $this->notificationService = new FeishuNotificationService(); } $applicant = User::find($order->applicant_id); if (!$applicant) { error_log("❌ notifyApplicant: 申请人不存在, applicant_id={$order->applicant_id}"); return; } // 根据业务类型获取详情链接 $detailUrl = $this->getDetailUrl($order); // 构建消息内容 $title = $action === 'approve' ? '您的申请已通过' : '您的申请已被拒绝'; $content = "**申请标题**: {$order->title}\n"; $content .= "**申请类型**: {$this->getBusinessTypeName($order->business_type)}\n"; if ($comment) { $content .= "**审批意见**: {$comment}\n"; } error_log("🔔 通知申请人: applicant_id={$applicant->id}, action={$action}"); $this->notificationService->setAppByUser($applicant->id) ->sendSystemNotification([ 'user_ids' => [$applicant->id], 'title' => $title, 'content' => $content, 'url' => $detailUrl, ]); } catch (\Exception $e) { error_log('❌ notifyApplicant 失败: ' . $e->getMessage()); } } /** * 通知下一节点的审批人 */ private function notifyNextApprovers(ApprovalOrder $order, array $nextNodes): void { try { if (!$this->notificationService) { $this->notificationService = new FeishuNotificationService(); } // 从 form_data 获取审批人列表 $formData = $order->form_data ?? []; $approvers = $formData['approvers'] ?? []; // 兼容两种格式 if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) { $approverIds = array_column($approvers, 'id'); } else { $approverIds = $approvers; } if (empty($approverIds)) { error_log("⚠️ notifyNextApprovers: 没有找到审批人, order_id={$order->id}"); return; } // 获取详情链接 $detailUrl = $this->getDetailUrl($order); // 业务类型名称 $businessTypeName = $this->getBusinessTypeName($order->business_type); error_log("🔔 通知下一节点审批人: order_id={$order->id}, approverIds=" . json_encode($approverIds)); // 获取第一个下一节点的 ID(用于消息映射) $nextNodeId = $nextNodes[0]['id'] ?? null; // 为每个审批人发送通知 foreach ($approverIds as $approverId) { try { $this->notificationService->setAppByUser($approverId) ->sendApprovalNotification([ 'user_id' => $approverId, 'applicant_id' => $order->applicant_id, 'title' => '新的审批待办', 'applicant' => $order->applicant->name, 'type' => $businessTypeName, 'content' => "**申请标题**: {$order->title}\n**申请人**: {$order->applicant->name}", 'url' => $detailUrl, 'order_id' => $order->id, 'node_id' => $nextNodeId, // 添加节点ID用于保存消息映射 ]); error_log("✅ 审批人通知成功: approver_id={$approverId}"); } catch (\Exception $e) { error_log("❌ 发送审批通知失败: approver_id={$approverId}, error=" . $e->getMessage()); } } } catch (\Exception $e) { error_log('❌ notifyNextApprovers 失败: ' . $e->getMessage()); } } /** * 获取详情链接 */ private function getDetailUrl(ApprovalOrder $order): string { $baseUrl = config('feishu_notification.base_url'); // 如果没有配置,使用当前请求构建 if (!$baseUrl) { // 检查是否在 CLI 环境 if (PHP_SAPI === 'cli' || !request()) { // CLI 环境:使用默认域名 $baseUrl = 'https://uniservice-mobile.hnxinyouhe.com'; } else { // Web 环境:检查反向代理的协议头 $scheme = request()->header('x-forwarded-proto') ?? request()->header('x-scheme') ?? 'https'; $host = request()->host(); $baseUrl = $scheme . '://' . $host; } } // 统一使用审批详情页,不再跳转到具体业务模块的详情页 return "{$baseUrl}/approval/detail/{$order->id}"; } /** * 获取业务类型名称 */ private function getBusinessTypeName(string $businessType): string { $typeNames = [ 'business_trip' => '出差申请', 'expense' => '费用报销', 'leave' => '请假申请', 'purchase' => '采购申请', ]; return $typeNames[$businessType] ?? $businessType; } /** * 自动通过申请人自己的审批节点 * 当审批人是申请人自己时,自动通过当前节点 */ private function autoApproveSelfNode(ApprovalOrder $order, int $approverId): ApprovalOrder { $currentNodeIds = $order->current_nodes ?? []; error_log("🔄 自审批自动通过 - 当前节点: " . json_encode($currentNodeIds)); // 检查订单是否已经结束 if (empty($currentNodeIds)) { error_log("❌ 自审批:订单已完成,current_nodes为空"); if ($order->status === ApprovalOrder::STATUS_APPROVED) { throw new \Exception('该申请已通过,无需重复操作'); } else if ($order->status === ApprovalOrder::STATUS_REJECTED) { throw new \Exception('该申请已被拒绝'); } else if ($order->status === ApprovalOrder::STATUS_CANCELLED) { throw new \Exception('该申请已被撤销'); } throw new \Exception('该申请已完成,无法重复操作'); } $currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first(); if (!$currentNode) { error_log("❌ 自审批:当前节点不存在,IDs: " . json_encode($currentNodeIds)); 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']); } }