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. 创建审批实例 $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', 'current_step' => 0, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); // 4. 创建审批记录(待办) $approverIds = $this->resolveApprovers($firstStep, (object)['initiator_id' => $initiatorId]); foreach ($approverIds as $approverId) { Db::table('approval_records')->insert([ 'instance_id' => $instanceId, 'step_name' => $firstStep['name'], 'approver_id' => $approverId, 'status' => 'pending', 'created_at' => date('Y-m-d H:i:s'), ]); } // 5. 通知审批人 $this->sendNotification($approverIds, (object)['id' => $instanceId, 'flow_type' => $flowType], 'pending', false); // 6. 通知抄送人(当前步骤) $this->notifyCCs($firstStep, (object)['id' => $instanceId, 'flow_type' => $flowType, 'form_data' => json_encode($formData)], 'pending'); return ['instance_id' => $instanceId]; } /** * 执行审批 */ public function approve(int $instanceId, int $approverId, string $action, string $comment = '') { $instance = Db::table('approval_instances')->where('id', $instanceId)->first(); if (!$instance) { throw new BusinessException("审批实例未找到"); } $steps = json_decode($instance->steps, true) ?: json_decode(Db::table('approval_flows')->where('type', $instance->flow_type)->value('steps'), true); $currentStep = $steps[$instance->current_step] ?? null; if (!$currentStep) { throw new BusinessException("当前步骤配置错误"); } // 检查用户是否有权限审批 if (!$this->canUserApproveStep($currentStep, $approverId, $instance)) { throw new BusinessException("您无权审批此步骤"); } // 更新审批记录 Db::table('approval_records') ->where('instance_id', $instanceId) ->where('approver_id', $approverId) ->where('status', 'pending') ->update([ '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, sequential) $stepCompleted = $this->isStepCompleted($instanceId, $instance->current_step, $currentStep['type']); if (!$stepCompleted) { // 步骤未完成,不进入下一步 return ['status' => 'step_pending']; } // 步骤已完成,进入下一步或结束 $nextStep = $this->getNextValidStep($steps, json_decode($instance->form_data, true), $instance->current_step + 1); if ($nextStep === null) { // 流程结束 Db::table('approval_instances')->where('id', $instanceId)->update([ 'status' => $action === 'approve' ? 'approved' : 'rejected', 'updated_at' => date('Y-m-d H:i:s'), ]); // 通知发起人结果 $this->sendNotification([$instance->initiator_id], $instance, $action, false); return ['status' => 'completed', 'result' => $action]; } // 进入下一步 $nextStepIndex = array_search($nextStep, $steps); Db::table('approval_instances')->where('id', $instanceId)->update([ 'current_step' => $nextStepIndex, 'updated_at' => date('Y-m-d H:i:s'), ]); // 创建下一步的审批记录 $nextApprovers = $this->resolveApprovers($nextStep, $instance); foreach ($nextApprovers as $approverId) { Db::table('approval_records')->insert([ 'instance_id' => $instanceId, 'step_name' => $nextStep['name'], 'approver_id' => $approverId, 'status' => 'pending', 'created_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)) { $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 '<': $actualValue < $value; case '>=': return $actualValue >= $value; case '<=': return $actualValue <= $value; default: throw new BusinessException("不支持的操作符: {$op}"); } } /** * 获取下一个有效步骤(考虑 condition) */ private function getNextValidStep(array $steps, array $formData, int $startIndex = 0): ?array { for ($i = $startIndex; $i < count($steps); $i++) { $step = $steps[$i]; if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) { return $step; } } 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 { $pendingCount = Db::table('approval_records') ->where('instance_id', $instanceId) ->where('status', 'pending') ->count(); if ($type === 'any') { return $pendingCount === 0; // 任一通过即可 } if ($type === 'all' || $type === 'sequential') { return $pendingCount === 0; // 所有人都必须审批 } 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 { // 简化查询:找到用户需要审批的实例ID // 更复杂的逻辑(如并行会签)需要更精细的查询 $instanceIds = Db::table('approval_records') ->where('approver_id', $userId) ->pluck('instance_id') ->unique() ->toArray(); if (empty($instanceIds)) { return []; } return Db::table('approval_instances') ->whereIn('id', $instanceIds) ->where('status', 'processing') ->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 { if ($step['type'] === 'parallel') { // 简化处理 $status = 'approved'; } 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'] = 'rejected'; Db::table('approval_instances')->where('id', $instance->id)->update($updateData); $this->notifyInitiator($instance, 'rejected_timeout'); } elseif ($flow->timeout_action === 'auto_approve') { $updateData['status'] = 'approved'; Db::table('approval_instances')->where('id', $instance->id)->update($updateData); $this->notifyInitiator($instance, 'approved_timeout'); } } } } // --- 辅助方法 --- // private function canUserApproveStep(array $step, int $userId, object $instance): bool // { // $approvers = $this->resolveApprovers($step, $instance); // return in_array($userId, $approvers); // } // private function resolveApprovers(array $step, object $instance): array // { // $resolved = []; // foreach ($step['approvers'] as $approver) { // if ($approver['type'] === 'role') { // // 示例:根据角色查找用户ID,你需要替换为实际逻辑 // if ($approver['value'] === 'admin') { // $resolved[] = 1; // 假设 admin 角色的用户ID是1 // } elseif ($approver['value'] === 'manager') { // $resolved[] = 2; $resolved[] = 3; // 假设有多个manager // } // } elseif ($approver['type'] === 'user') { // $resolved[] = (int)$approver['value']; // } elseif ($approver['type'] === 'initiator_leader') { // // 示例:获取发起人的领导 // $resolved[] = 4; // 假设领导ID是4 // } // } // return array_unique($resolved); // } // private function resolveApprovers(array $step, object $instance): array // { // $resolved = []; // foreach ($step['approvers'] as $approverConfig) { // $approverIds = []; // switch ($approverConfig['type']) { // // 1. 按角色查找 // case 'role': // $roleCode = $approverConfig['value']; // // 所有拥有该角色的用户ID // $approverIds = $this->getUserIdsByRole($roleCode); // break; // // 2. 按具体用户ID // case 'user': // $approverIds = [(int)$approverConfig['value']]; // break; // // 3. 按发起人的直属领导 (业务一般是组长) // case 'initiator_leader': // // 用户表中 report_to 字段 // $approverIds = $this->getLeaderIdByUserId($instance->initiator_id); // if ($approverIds) { // $approverIds = [$approverIds]; // } else { // $approverIds = []; // 没有领导,跳过 // } // break; // // 4. 按业务字段关联的用户 (高级功能) // case 'field_user': // $fieldName = $approverConfig['value']; // 如 'project_manager_id' // $formData = json_decode($instance->form_data, true); // if (isset($formData[$fieldName])) { // $approverIds = [(int)$formData[$fieldName]]; // } else { // $approverIds = []; // } // break; // // 5. 按组织架构层级 (如发起人所在部门的负责人) // case 'dept_head': // $deptId = $approverConfig['value']; // 也可以从 instance 或 formData 中获取 // $approverIds = $this->getDeptHeadId($deptId); // if ($approverIds) { // $approverIds = [$approverIds]; // } else { // $approverIds = []; // } // break; // default: // throw new BusinessException("不支持的审批人类型: {$approverConfig['type']}"); // } // $resolved = array_merge($resolved, $approverIds); // } // return array_unique($resolved); // } // 角色层 private function getUserIdsByRole(string $roleCode): array { // user_roles 表和 roles 表 return Db::table('user_roles as ur') ->join('roles as r', 'ur.role_id', '=', 'r.id') ->where('r.code', $roleCode) // 假设 roles 表有 code 字段 ->pluck('ur.user_id') ->toArray(); } // 领导层 private function getLeaderIdByUserId(int $userId): ?int { // users 表 report_to 字段 return Db::table('user')->where('id', $userId)->value('report_to'); } // 组织架构层 private function getDeptHeadId(int $deptId): ?int { // departments 表 head_user_id 字段(部门逻辑暂时不用,根据后续业务实际情况开发) return Db::table('departments')->where('id', $deptId)->value('head_user_id'); } private function calculateNextStepIndex(int $instanceId, int $currentStepIndex, array $currentStep): int { // 对于 sequential 和 any,单次 approve 即进入下一步 if ($currentStep['type'] === 'sequential' || $currentStep['type'] === 'any') { return $currentStepIndex + 1; } elseif ($currentStep['type'] === 'parallel') { // 对于并行会签,需要检查是否所有审批人都已 approve // 这里简化处理,实际需要精确计算 // 假设只要有一次 approve 就算完成(不准确,仅为示例) // 正确做法是查询所有 approvers 是否都 approve 了 return $currentStepIndex + 1; } return $currentStepIndex + 1; } private function notifyNextApprovers(int $instanceId) { // 实现你的通知逻辑,如发送站内信、邮件、企业微信等 // 可以查询 instance_id 对应的当前步骤审批人 echo "通知下一个审批人 (Instance ID: {$instanceId})\n"; } private function notifyInitiator(object $instance, string $result) { // 通知发起人审批结果 echo "通知发起人 {$instance->initiator_id} 审批结果: {$result}\n"; } private function notifyAllApprovers(object $instance, string $status) { // 通知所有相关审批人(如撤回时) echo "通知所有审批人,实例 {$instance->id} 状态变为: {$status}\n"; } private function notifyCc(int $instanceId) { // 通知抄送人 echo "通知抄送人 (Instance ID: {$instanceId})\n"; } }