alias("a") ->join(['school_personnel' => 'b'],'a.applicant_id = b.id','left') ->join(['school_personnel' => 'c'],'a.current_approver_id = c.id','left') ->where($where) ->field($field) ->order($order) ->page($page, $limit) ->select() ->toArray(); $count = (new SchoolApprovalProcess())->where($where)->count(); return [ 'list' => $list, 'count' => $count ]; } /** * 获取审批流程详情 * @param int $id * @return array */ public function getInfo(int $id): array { $info = (new SchoolApprovalProcess()) ->alias("a") ->join(['school_personnel' => 'b'],'a.applicant_id = b.id','left') ->join(['school_personnel' => 'c'],'a.current_approver_id = c.id','left') ->with(['participants']) ->where(['a.id' => $id]) ->field('a.*,b.name as applicant_name,c.name as current_approver_name') ->find(); if (empty($info)) { return []; } $info = $info->toArray(); $Personnel = new Personnel(); foreach ($info['participants'] as $key => $value) { $info['participants'][$key]['name'] = $Personnel->where(['id' => $value['participant_id']])->value('name'); } return $info; } /** * 创建审批流程 * @param array $data * @param int $config_id 审批配置ID * @return int * @throws \Exception */ public function create(array $data, int $config_id): int { Db::startTrans(); try { // 获取审批配置详情 $config_info = (new SchoolApprovalConfigService())->getInfo($config_id); if (empty($config_info)) { throw new Exception('审批配置不存在'); } // 创建审批流程 $process = [ 'process_name' => $data['process_name'], 'applicant_id' => $data['applicant_id'], 'application_time' => date("Y-m-d H:i:s"), 'current_approver_id' => 0, // 初始时为0,后面会更新 'approval_status' => SchoolApprovalProcess::STATUS_PENDING, 'remarks' => $data['remarks'] ?? '', 'business_type' => $data['business_type'] ?? '', 'business_id' => $data['business_id'] ?? 0, 'business_data' => isset($data['business_data']) ? json_encode($data['business_data']) : '' ]; $process_id = (new SchoolApprovalProcess())->insertGetId($process); // 创建审批参与人 $participants = []; foreach ($config_info['nodes'] as $sequence => $node) { $approver_ids = explode(',', $node['approver_ids']); // 动态获取审批人 $actual_approvers = $this->getDynamicApprovers($node['approver_type'], $approver_ids); foreach ($actual_approvers as $approver_id) { $participants[] = [ 'process_id' => $process_id, 'participant_id' => $approver_id, 'sequence' => $node['sequence'], 'status' => SchoolApprovalParticipants::STATUS_PENDING, 'sign_type' => $node['sign_type'] ]; } } if (!empty($participants)) { (new SchoolApprovalParticipants())->insertAll($participants); // 更新当前审批人为第一个审批人 $first_participant = (new SchoolApprovalParticipants()) ->where(['process_id' => $process_id]) ->order('sequence', 'asc') ->find(); if (!empty($first_participant)) { (new SchoolApprovalProcess())->where(['id' => $process_id]) ->update(['current_approver_id' => $first_participant['participant_id']]); } } // 发送待审批通知给第一个审批人 if (!empty($first_participant)) { $this->sendApprovalNotification($process_id, $first_participant['participant_id'], 'pending'); } Db::commit(); return $process_id; } catch (\Exception $e) { Db::rollback(); throw new Exception($e->getMessage()); } } /** * 创建人员添加审批流程 * @param array $personnelData 人员数据 * @param int $applicantId 申请人ID * @param int $configId 审批配置ID * @return int * @throws \Exception */ public function createPersonnelApproval(array $personnelData, int $applicantId, int $configId): int { // 创建审批流程数据 $processData = [ 'process_name' => '人员添加申请 - ' . $personnelData['name'], 'applicant_id' => $applicantId, 'remarks' => '申请添加新员工:' . $personnelData['name'] . ',职位:' . ($personnelData['position'] ?? '未指定'), 'business_type' => 'personnel_add', 'business_id' => 0, // 暂时为0,等人员创建后更新 'business_data' => $personnelData ]; return $this->create($processData, $configId); } /** * 审批 * @param int $process_id 流程ID * @param int $approver_id 审批人ID * @param string $status 审批状态 * @param string $remarks 备注 * @return bool * @throws \Exception */ public function approve(int $process_id, int $approver_id, string $status, string $remarks = ''): bool { Db::startTrans(); try { // 获取审批流程 $process_info = (new SchoolApprovalProcess())->where(['id' => $process_id])->find(); if (empty($process_info)) { throw new Exception('审批流程不存在'); } // 检查是否当前审批人 if ($process_info['current_approver_id'] != $approver_id) { throw new Exception('您不是当前审批人'); } // 检查流程状态 if ($process_info['approval_status'] != SchoolApprovalProcess::STATUS_PENDING) { throw new Exception('该审批流程已完成'); } // 获取当前审批节点 $current_participant = (new SchoolApprovalParticipants()) ->where([ 'process_id' => $process_id, 'participant_id' => $approver_id, 'status' => SchoolApprovalParticipants::STATUS_PENDING ]) ->find(); if (empty($current_participant)) { throw new Exception('审批节点信息错误'); } // 更新当前审批人状态 (new SchoolApprovalParticipants())->where(['id' => $current_participant['id']]) ->update([ 'status' => $status, 'remarks' => $remarks ]); // 记录审批历史 $this->recordApprovalHistory($process_id, $approver_id, $current_participant['sequence'], $status, $remarks); // 如果拒绝,直接更新整个流程状态为拒绝 if ($status == SchoolApprovalParticipants::STATUS_REJECTED) { (new SchoolApprovalProcess())->where(['id' => $process_id]) ->update([ 'approval_status' => SchoolApprovalProcess::STATUS_REJECTED, 'approval_time' => time(), 'remarks' => $remarks ]); // 处理拒绝后的业务逻辑 $this->handleApprovalRejected($process_id); // 发送审批拒绝通知给申请人 $this->sendApprovalNotification($process_id, $process_info['applicant_id'], 'rejected'); Db::commit(); return true; } // 检查当前节点是否完成 if (!$this->isCurrentNodeCompleted($process_id, $current_participant['sequence'])) { // 当前节点未完成(会签情况下还有其他人未审批),等待其他人审批 Db::commit(); return true; } // 获取下一个审批节点 $next_participant = (new SchoolApprovalParticipants()) ->where([ 'process_id' => $process_id, 'status' => SchoolApprovalParticipants::STATUS_PENDING ]) ->order('sequence', 'asc') ->find(); if (empty($next_participant)) { // 没有下一个审批人,流程结束,标记为已通过 (new SchoolApprovalProcess())->where(['id' => $process_id]) ->update([ 'approval_status' => SchoolApprovalProcess::STATUS_APPROVED, 'approval_time' => time() ]); // 处理业务逻辑 $this->handleApprovalCompleted($process_id); // 发送审批完成通知给申请人 $this->sendApprovalNotification($process_id, $process_info['applicant_id'], 'approved'); } else { // 更新当前审批人为下一个审批人 (new SchoolApprovalProcess())->where(['id' => $process_id]) ->update(['current_approver_id' => $next_participant['participant_id']]); // 发送待审批通知给下一个审批人 $this->sendApprovalNotification($process_id, $next_participant['participant_id'], 'pending'); } Db::commit(); return true; } catch (\Exception $e) { Db::rollback(); throw new Exception($e->getMessage()); } } /** * 撤销审批流程 * @param int $process_id 流程ID * @param int $applicant_id 申请人ID * @return bool * @throws \Exception */ public function cancel(int $process_id, int $applicant_id): bool { Db::startTrans(); try { // 获取审批流程 $process_info = (new SchoolApprovalProcess())->where(['id' => $process_id])->find(); if (empty($process_info)) { throw new Exception('审批流程不存在'); } // 检查是否申请人 if ($process_info['applicant_id'] != $applicant_id) { throw new Exception('您不是该流程的申请人'); } // 检查流程状态 if ($process_info['approval_status'] != SchoolApprovalProcess::STATUS_PENDING) { throw new Exception('该审批流程已完成,无法撤销'); } // 更新流程状态为已拒绝(撤销) (new SchoolApprovalProcess())->where(['id' => $process_id]) ->update([ 'approval_status' => SchoolApprovalProcess::STATUS_REJECTED, 'approval_time' => time(), 'remarks' => '申请人撤销' ]); // 更新所有待审批节点为已拒绝 (new SchoolApprovalParticipants())->where([ 'process_id' => $process_id, 'status' => SchoolApprovalParticipants::STATUS_PENDING ])->update([ 'status' => SchoolApprovalParticipants::STATUS_REJECTED, 'remarks' => '申请人撤销' ]); Db::commit(); return true; } catch (\Exception $e) { Db::rollback(); throw new Exception($e->getMessage()); } } /** * 处理审批完成后的业务逻辑 * @param int $process_id * @throws \Exception */ private function handleApprovalCompleted(int $process_id): void { // 获取流程信息 $process = (new SchoolApprovalProcess())->where(['id' => $process_id])->find(); if (empty($process)) { throw new Exception('流程信息不存在'); } // 根据业务类型处理 switch ($process['business_type']) { case 'personnel_add': $this->handlePersonnelAddApproval($process); break; default: // 其他业务类型的处理逻辑 break; } } /** * 处理人员添加审批完成 * @param $process * @throws \Exception */ private function handlePersonnelAddApproval($process): void { if (empty($process['business_data'])) { throw new Exception('人员数据不存在'); } $personnelData = json_decode($process['business_data'], true); if (empty($personnelData)) { throw new Exception('人员数据格式错误'); } try { // 调用API人员服务创建正式人员记录(避免info字段问题) $personnelService = new \app\service\api\apiService\PersonnelService(); // 准备人员数据 $createData = $personnelData; $createData['status'] = 2; // 设置为已审核状态 // 创建人员记录 $result = $personnelService->addPersonnel($createData); if (!$result['code']) { throw new \Exception($result['msg']); } $personnelId = $result['data']['personnel_id']; // 更新流程的business_id为实际创建的人员ID (new SchoolApprovalProcess())->where(['id' => $process['id']]) ->update(['business_id' => $personnelId]); } catch (\Exception $e) { throw new Exception('创建人员记录失败:' . $e->getMessage()); } } /** * 处理审批拒绝后的业务逻辑 * @param int $process_id * @throws \Exception */ private function handleApprovalRejected(int $process_id): void { // 获取流程信息 $process = (new SchoolApprovalProcess())->where(['id' => $process_id])->find(); if (empty($process)) { return; } // 根据业务类型处理 switch ($process['business_type']) { case 'personnel_add': // 人员添加被拒绝,不需要特殊处理 break; default: // 其他业务类型的处理逻辑 break; } } /** * 检查当前节点是否已完成 * @param int $process_id 流程ID * @param int $sequence 节点序号 * @return bool */ private function isCurrentNodeCompleted(int $process_id, int $sequence): bool { // 获取当前节点的所有参与人 $participants = (new SchoolApprovalParticipants()) ->where([ 'process_id' => $process_id, 'sequence' => $sequence ]) ->select() ->toArray(); if (empty($participants)) { return true; } // 获取第一个参与人的审批类型(同一节点的审批类型应该一致) $sign_type = $participants[0]['sign_type']; if ($sign_type == SchoolApprovalParticipants::SIGN_TYPE_OR) { // 或签:只要有一个人通过即可 foreach ($participants as $participant) { if ($participant['status'] == SchoolApprovalParticipants::STATUS_APPROVED) { return true; } } return false; } else { // 会签:需要所有人都通过 foreach ($participants as $participant) { if ($participant['status'] == SchoolApprovalParticipants::STATUS_PENDING) { return false; // 还有人未审批 } if ($participant['status'] == SchoolApprovalParticipants::STATUS_REJECTED) { return true; // 有人拒绝,节点结束 } } return true; // 所有人都已审批通过 } } /** * 获取下一个审批人 * @param int $process_id 流程ID * @param int $current_sequence 当前节点序号 * @return int|null */ private function getNextApprover(int $process_id, int $current_sequence): ?int { // 获取下一个序号的第一个审批人 $next_participant = (new SchoolApprovalParticipants()) ->where([ 'process_id' => $process_id, 'sequence' => ['>', $current_sequence], 'status' => SchoolApprovalParticipants::STATUS_PENDING ]) ->order('sequence', 'asc') ->find(); return $next_participant ? $next_participant['participant_id'] : null; } /** * 动态获取审批人列表 * @param string $approver_type 审批人类型 * @param array $approver_ids 审批人ID数组 * @return array */ private function getDynamicApprovers(string $approver_type, array $approver_ids): array { $approvers = []; switch ($approver_type) { case 'user': // 直接返回用户ID $approvers = $approver_ids; break; case 'role': // 根据角色获取用户 $roleUserModel = new \app\model\admin\AdminRole(); foreach ($approver_ids as $role_id) { $users = $roleUserModel->where(['role_id' => $role_id])->column('admin_id'); $approvers = array_merge($approvers, $users); } break; case 'department': // 根据部门获取用户 $personnelModel = new Personnel(); foreach ($approver_ids as $dept_id) { $users = $personnelModel->where(['department_id' => $dept_id])->column('id'); $approvers = array_merge($approvers, $users); } break; } return array_unique($approvers); } /** * 记录审批历史 * @param int $process_id 流程ID * @param int $participant_id 审批人ID * @param int $sequence 审批序号 * @param string $status 审批状态 * @param string $remarks 审批意见 * @return void */ private function recordApprovalHistory(int $process_id, int $participant_id, int $sequence, string $status, string $remarks): void { $action = ($status == SchoolApprovalParticipants::STATUS_APPROVED) ? SchoolApprovalHistory::ACTION_APPROVE : SchoolApprovalHistory::ACTION_REJECT; $history_status = ($status == SchoolApprovalParticipants::STATUS_APPROVED) ? SchoolApprovalHistory::STATUS_APPROVED : SchoolApprovalHistory::STATUS_REJECTED; (new SchoolApprovalHistory())->insert([ 'process_id' => $process_id, 'participant_id' => $participant_id, 'sequence' => $sequence, 'action' => $action, 'status' => $history_status, 'remarks' => $remarks ]); } /** * 获取审批历史 * @param int $process_id 流程ID * @return array */ public function getApprovalHistory(int $process_id): array { $history = (new SchoolApprovalHistory()) ->alias('h') ->join(['school_personnel' => 'p'], 'h.participant_id = p.id', 'left') ->where(['h.process_id' => $process_id]) ->field('h.*, p.name as participant_name') ->order('h.sequence asc, h.created_at asc') ->select() ->toArray(); return $history; } /** * 发送审批通知 * @param int $process_id 流程ID * @param int $to_user_id 接收人ID * @param string $type 通知类型 pending|approved|rejected * @return void */ private function sendApprovalNotification(int $process_id, int $to_user_id, string $type): void { try { // 获取流程信息 $process = (new SchoolApprovalProcess()) ->alias('p') ->join(['school_personnel' => 'applicant'], 'p.applicant_id = applicant.id', 'left') ->where(['p.id' => $process_id]) ->field('p.*, applicant.name as applicant_name') ->find(); if (empty($process)) { return; } // 根据通知类型设置消息内容 $title = ''; $content = ''; switch ($type) { case 'pending': $title = '待审批通知'; $content = "您有一个审批流程需要处理:{$process['process_name']},申请人:{$process['applicant_name']}"; break; case 'approved': $title = '审批通过通知'; $content = "您的审批申请已通过:{$process['process_name']}"; break; case 'rejected': $title = '审批拒绝通知'; $content = "您的审批申请已被拒绝:{$process['process_name']}"; break; default: return; } // 发送系统消息 (暂时注释,等待聊天模块完善) // $messageModel = new \app\model\school_chat\SchoolChatMessages(); // $messageModel->insert([ // 'from_type' => 'system', // 'from_id' => 0, // 'to_id' => $to_user_id, // 'friend_id' => 0, // 'message_type' => 'notification', // 'content' => $content, // 'title' => $title, // 'business_id' => $process_id, // 'business_type' => 'approval_process', // 'is_read' => 0, // 'created_at' => date('Y-m-d H:i:s') // ]); } catch (\Exception $e) { // 发送通知失败不影响主流程,仅记录日志 \think\facade\Log::error('发送审批通知失败:' . $e->getMessage()); } } }