where('type', $flowType) ->where('is_active', 1) ->first(); if (!$flow) { throw new BusinessException("审批流程未找到或已停用: {$flowType}"); } $steps = json_decode($flow->steps, true); if (!is_array($steps) || empty($steps)) { throw new BusinessException("流程步骤配置错误"); } // 2. 查找第一个有效步骤(考虑 condition) $firstStep = $this->getNextValidStep($steps, $formData); if (!$firstStep) { throw new BusinessException("未找到可执行的审批步骤"); } // 3. 计算截止时间 $deadlineAt = null; if ($flow->timeout_hours > 0) { $deadlineAt = date('Y-m-d H:i:s', time() + $flow->timeout_hours * 3600); } // 4. 创建审批实例 $instanceId = Db::table('approval_instances')->insertGetId([ 'flow_type' => $flowType, 'biz_type' => $bizType, 'biz_id' => $bizId, 'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE), 'initiator_id' => $initiatorId, 'status' => 'processing', // processing 表示正在进行中 'current_step' => 0, 'deadline_at' => $deadlineAt, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); // 5. 创建审批记录(待办) $approverIds = $this->resolveApprovers($firstStep, (object)[ 'initiator_id' => $initiatorId, 'form_data' => json_encode($formData) ]); foreach ($approverIds as $approverId) { Db::table('approval_records')->insert([ 'instance_id' => $instanceId, 'approver_id' => $approverId, 'step_index' => 0, 'action' => '', // 未操作时为空 'status' => 'pending', 'approved_at' => date('Y-m-d H:i:s', 0), // 未审批时设置为初始时间 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); } // 6. 通知审批人 $instance = (object)[ 'id' => $instanceId, 'flow_type' => $flowType, 'form_data' => json_encode($formData), 'initiator_id' => $initiatorId ]; $this->sendNotification($approverIds, $instance, 'pending', false); // 7. 通知抄送人(当前步骤) $this->notifyCCs($firstStep, $instance, 'pending'); return ['instance_id' => $instanceId]; } /** * 执行审批 */ public function approve(int $instanceId, int $approverId, string $action, string $comment = '') { // 验证操作是否有效 if (!in_array($action, ['approve', 'reject'])) { throw new BusinessException("无效的审批操作"); } $instance = Db::table('approval_instances')->where('id', $instanceId)->first(); if (!$instance) { throw new BusinessException("审批实例未找到"); } // 获取流程定义 $flow = Db::table('approval_flows') ->where('type', $instance->flow_type) ->first(); if (!$flow) { throw new BusinessException("审批流程定义未找到"); } $steps = json_decode($flow->steps, true); $currentStepIndex = $instance->current_step; $currentStep = $steps[$currentStepIndex] ?? null; if (!$currentStep) { throw new BusinessException("当前步骤配置错误"); } // 检查用户是否有权限审批 if (!$this->canUserApproveStep($currentStep, $approverId, $instance)) { throw new BusinessException("您无权审批此步骤"); } // 检查用户是否已经审批过 $existingRecord = Db::table('approval_records') ->where('instance_id', $instanceId) ->where('approver_id', $approverId) ->where('step_index', $currentStepIndex) ->first(); if ($existingRecord && $existingRecord->status !== 'pending') { throw new BusinessException("您已经审批过此步骤"); } // 更新审批记录 Db::table('approval_records') ->where('instance_id', $instanceId) ->where('approver_id', $approverId) ->where('step_index', $currentStepIndex) ->where('status', 'pending') ->update([ 'action' => $action, 'status' => $action, // 状态与操作一致 'comment' => $comment, 'approved_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); // 检查当前步骤是否完成(根据 type: any, all) $stepCompleted = $this->isStepCompleted($instanceId, $currentStepIndex, $currentStep['type']); if (!$stepCompleted) { // 步骤未完成,不进入下一步 return ['status' => 'step_pending']; } // 步骤已完成,进入下一步或结束 $nextStepIndex = $this->getNextStepIndex($steps, json_decode($instance->form_data, true), $currentStepIndex); if ($nextStepIndex === null) { // 流程结束 Db::table('approval_instances')->where('id', $instanceId)->update([ 'status' => $action, // 最终状态与最后一次操作一致 'finished_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); // 通知发起人结果 $this->sendNotification([$instance->initiator_id], $instance, $action, false); return ['status' => 'completed', 'result' => $action]; } // 进入下一步 Db::table('approval_instances')->where('id', $instanceId)->update([ 'current_step' => $nextStepIndex, 'updated_at' => date('Y-m-d H:i:s'), ]); // 获取下一步信息 $nextStep = $steps[$nextStepIndex]; // 创建下一步的审批记录 $nextApprovers = $this->resolveApprovers($nextStep, $instance); foreach ($nextApprovers as $approverId) { Db::table('approval_records')->insert([ 'instance_id' => $instanceId, 'approver_id' => $approverId, 'step_index' => $nextStepIndex, 'action' => '', 'status' => 'pending', 'approved_at' => date('Y-m-d H:i:s', 0), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); } // 通知下一步审批人 $this->sendNotification($nextApprovers, $instance, 'pending', false); // 通知下一步抄送人 $this->notifyCCs($nextStep, $instance, 'pending'); return ['status' => 'next_step', 'step_name' => $nextStep['name']]; } /** * 解析审批人 */ private function resolveApprovers(array $step, object $instance): array { $resolved = []; $formData = json_decode($instance->form_data ?? '{}', true); foreach ($step['approvers'] as $approverConfig) { $approverIds = []; switch ($approverConfig['type']) { case 'role': $approverIds = $this->getUserIdsByRole($approverConfig['value']); break; case 'user': $approverIds = [(int)$approverConfig['value']]; break; case 'initiator_leader': $leaderId = $this->getLeaderIdByUserId($instance->initiator_id); if ($leaderId) $approverIds = [$leaderId]; break; case 'field_user': if (isset($formData[$approverConfig['value']])) { $approverIds = [(int)$formData[$approverConfig['value']]]; } break; case 'dept_head': $approverIds = $this->getDeptHeadId($approverConfig['value']); if ($approverIds) $approverIds = [$approverIds]; break; default: throw new BusinessException("不支持的审批人类型: {$approverConfig['type']}"); } $resolved = array_merge($resolved, $approverIds); } return array_unique(array_filter($resolved)); } /** * 通知抄送人 */ private function notifyCCs(array $step, object $instance, string $action = 'pending') { $ccConfig = $step['cc'] ?? []; $ccUserIds = []; $formData = json_decode($instance->form_data ?? '{}', true); foreach ($ccConfig as $cc) { switch ($cc['type']) { case 'role': $ccUserIds = array_merge($ccUserIds, $this->getUserIdsByRole($cc['value'])); break; case 'user': $ccUserIds[] = (int)$cc['value']; break; case 'initiator_leader': $leaderId = $this->getLeaderIdByUserId($instance->initiator_id); if ($leaderId) $ccUserIds[] = $leaderId; break; case 'field_user': if (isset($formData[$cc['value']])) { $ccUserIds[] = (int)$formData[$cc['value']]; } break; case 'dept_head': $deptHeadId = $this->getDeptHeadId($cc['value']); if ($deptHeadId) $ccUserIds[] = $deptHeadId; break; default: break; } } $ccUserIds = array_unique(array_filter($ccUserIds)); if (!empty($ccUserIds)) { // 记录抄送信息到数据库 foreach ($ccUserIds as $userId) { // 检查是否已存在抄送记录 $exists = Db::table('approval_ccs') ->where('instance_id', $instance->id) ->where('user_id', $userId) ->exists(); if (!$exists) { Db::table('approval_ccs')->insert([ 'instance_id' => $instance->id, 'user_id' => $userId, 'notified_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); } } // 发送通知 $this->sendNotification($ccUserIds, $instance, $action, true); } } /** * 评估条件 */ private function evaluateCondition(?string $condition, array $formData): bool { if (!$condition) return true; $pattern = '/^(\w+)\s*(==|!=|>|<|>=|<=)\s*(.+)$/'; if (!preg_match($pattern, $condition, $matches)) { throw new BusinessException("条件格式错误: {$condition}"); } $field = $matches[1]; $op = $matches[2]; $value = trim($matches[3], "\"'"); $actualValue = $formData[$field] ?? null; if (is_numeric($value) && is_numeric($actualValue)) { $value = (float)$value; $actualValue = (float)$actualValue; } switch ($op) { case '==': return $actualValue == $value; case '!=': return $actualValue != $value; case '>': return $actualValue > $value; case '<': return $actualValue < $value; case '>=': return $actualValue >= $value; case '<=': return $actualValue <= $value; default: throw new BusinessException("不支持的操作符: {$op}"); } } /** * 获取下一个有效步骤索引(考虑 condition) */ private function getNextStepIndex(array $steps, array $formData, int $currentStepIndex): ?int { for ($i = $currentStepIndex + 1; $i < count($steps); $i++) { $step = $steps[$i]; if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) { return $i; } } return null; } /** * 检查用户是否有权限审批 */ private function canUserApproveStep(array $step, int $userId, object $instance): bool { $approvers = $this->resolveApprovers($step, $instance); return in_array($userId, $approvers); } /** * 检查当前步骤是否完成 */ private function isStepCompleted(int $instanceId, int $stepIndex, string $type): bool { // 获取当前步骤的所有审批记录 $records = Db::table('approval_records') ->where('instance_id', $instanceId) ->where('step_index', $stepIndex) ->get(); if ($records->isEmpty()) { return false; } switch ($type) { case 'any': // 任意一人审批即可(approve或reject都算完成) foreach ($records as $record) { if ($record->status !== 'pending') { return true; } } return false; case 'all': // 所有人都必须审批 foreach ($records as $record) { if ($record->status === 'pending') { return false; } } return true; default: return false; } } /** * 发送通知(你可以替换为你的通知系统) */ private function sendNotification(array $userIds, object $instance, string $action, bool $isCC) { $actionText = [ 'pending' => '待审批', 'approve' => '已通过', 'reject' => '已拒绝' ][$action] ?? $action; $prefix = $isCC ? '[抄送]' : ''; $message = "{$prefix}您有一条审批 {$actionText}:流程ID {$instance->id},类型 {$instance->flow_type}"; // TODO: 调用你的消息系统,如邮件、站内信、企业微信等 // 示例:MessageService::send($userIds, $message); echo "通知用户 " . implode(',', $userIds) . ":$message\n"; } /** * 获取用户待审批列表 * @param int $userId 用户ID * @return array */ public function getPendingApprovals(int $userId): array { return Db::table('approval_records as ar') ->join('approval_instances as ai', 'ar.instance_id', '=', 'ai.id') ->where('ar.approver_id', $userId) ->where('ar.status', 'pending') ->where('ai.status', 'processing') ->select('ai.*') ->get() ->toArray(); } /** * 获取审批历史记录 * @param int $instanceId * @return array */ public function getApprovalHistory(int $instanceId): array { return Db::table('approval_records') ->where('instance_id', $instanceId) ->orderBy('step_index') ->orderBy('approved_at') ->get() ->toArray(); } /** * 获取审批流程图数据 (简化版) * @param int $instanceId * @return array */ public function getFlowDiagramData(int $instanceId): array { $instance = Db::table('approval_instances')->find($instanceId); if (!$instance) return []; $flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first(); if (!$flow) return []; $steps = json_decode($flow->steps, true); $records = Db::table('approval_records') ->where('instance_id', $instanceId) ->orderBy('step_index') ->orderBy('approved_at') ->get() ->groupBy('step_index'); $diagramData = []; foreach ($steps as $index => $step) { $status = 'pending'; $actions = $records->get($index, collect())->toArray(); if (!empty($actions)) { $hasReject = collect($actions)->contains('action', 'reject'); if ($hasReject) { $status = 'rejected'; } else { $status = 'approved'; } } elseif ($index < $instance->current_step) { $status = 'approved'; } elseif ($index == $instance->current_step) { $status = 'processing'; } $diagramData[] = [ 'index' => $index, 'name' => $step['name'], 'type' => $step['type'], 'status' => $status, 'actions' => $actions ]; } return $diagramData; } /** * 处理超时的审批实例 (定时任务调用) */ public function handleTimeouts() { $now = date('Y-m-d H:i:s'); $expiredInstances = Db::table('approval_instances') ->where('status', 'processing') ->where('deadline_at', '<=', $now) ->get(); foreach ($expiredInstances as $instance) { $flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first(); if ($flow && $flow->timeout_action) { $updateData = [ 'finished_at' => $now, 'updated_at' => $now ]; if ($flow->timeout_action === 'auto_reject') { $updateData['status'] = 'reject'; } elseif ($flow->timeout_action === 'auto_approve') { $updateData['status'] = 'approve'; } Db::table('approval_instances')->where('id', $instance->id)->update($updateData); $this->notifyInitiator($instance, $flow->timeout_action); } } } // --- 辅助方法 --- private function getUserIdsByRole(string $roleCode): array { return Db::table('user_roles as ur') ->join('roles as r', 'ur.role_id', '=', 'r.id') ->where('r.code', $roleCode) ->pluck('ur.user_id') ->toArray(); } private function getLeaderIdByUserId(int $userId): ?int { return Db::table('user')->where('id', $userId)->value('report_to'); } private function getDeptHeadId(int $deptId): ?int { return Db::table('departments')->where('id', $deptId)->value('head_user_id'); } private function notifyInitiator(object $instance, string $result) { echo "通知发起人 {$instance->initiator_id} 审批结果: {$result}\n"; } }