true, 'instance_id' => ...] 或抛出异常 */ public function start(string $flowType, string $bizType, string $bizId, array $formData, int $initiatorId): array { // 1. 获取流程定义 $flow = Db::table('approval_flows') ->where('type', $flowType) ->where('is_active', 1) ->first(); if (!$flow) { throw new BusinessException("审批流程 '{$flowType}' 不存在或已停用"); } // 2. 创建审批实例 $instanceId = Db::table('approval_instances')->insertGetId([ 'flow_type' => $flowType, 'biz_id' => $bizId, 'biz_type' => $bizType, 'form_data' => json_encode($formData), 'initiator_id' => $initiatorId, 'status' => 'processing', 'current_step' => 0, 'deadline_at' => $flow->timeout_hours > 0 ? (new DateTime())->modify("+{$flow->timeout_hours} hours")->format('Y-m-d H:i:s') : null, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); // 3. 通知下一个审批人 $this->notifyNextApprovers($instanceId); return ['success' => true, 'instance_id' => $instanceId]; } /** * 执行审批操作 * @param int $instanceId 审批实例ID * @param int $approverId 审批人ID * @param string $action 操作 (approve, reject, revoke) * @param string $comment 审批意见 * @return array 操作结果 */ public function approve(int $instanceId, int $approverId, string $action, string $comment = ''): array { // 1. 获取实例和流程 $instance = Db::table('approval_instances')->find($instanceId); if (!$instance) { throw new BusinessException("审批实例不存在"); } if ($instance->status !== 'processing') { 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; if ($currentStepIndex >= count($steps)) { throw new BusinessException("审批流程已结束"); } $currentStep = $steps[$currentStepIndex]; // 2. 验证权限和条件 if (!$this->canUserApproveStep($currentStep, $approverId, $instance)) { throw new BusinessException("您无权审批此步骤"); } // 3. 记录审批行为 Db::table('approval_records')->insert([ 'instance_id' => $instanceId, 'approver_id' => $approverId, 'step_index' => $currentStepIndex, 'action' => $action, 'comment' => $comment, 'approved_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'), ]); // 4. 处理审批结果 $updateData = ['updated_at' => date('Y-m-d H:i:s')]; if ($action === 'reject') { $updateData['status'] = 'rejected'; $updateData['finished_at'] = date('Y-m-d H:i:s'); Db::table('approval_instances')->where('id', $instanceId)->update($updateData); $this->notifyInitiator($instance, 'rejected'); return ['result' => 'rejected']; } if ($action === 'revoke') { if ($instance->initiator_id !== $approverId || $instance->status !== 'processing') { throw new BusinessException("无权撤回或状态不允许"); } $updateData['status'] = 'revoked'; $updateData['finished_at'] = date('Y-m-d H:i:s'); Db::table('approval_instances')->where('id', $instanceId)->update($updateData); $this->notifyAllApprovers($instance, 'revoked'); return ['result' => 'revoked']; } // 5. 处理 approve 逻辑 $nextStepIndex = $this->calculateNextStepIndex($instanceId, $currentStepIndex, $currentStep); $updateData['current_step'] = $nextStepIndex; if ($nextStepIndex >= count($steps)) { $updateData['status'] = 'approved'; $updateData['finished_at'] = date('Y-m-d H:i:s'); Db::table('approval_instances')->where('id', $instanceId)->update($updateData); $this->notifyInitiator($instance, 'approved'); $this->notifyCc($instanceId); } else { Db::table('approval_instances')->where('id', $instanceId)->update($updateData); $this->notifyNextApprovers($instanceId); } return ['result' => 'approved', 'next_step' => $nextStepIndex]; } /** * 获取用户待审批列表 * @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"; } }