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) ->where('round', (int)$instance->round) ->orderBy('step_index')->orderByRaw('COALESCE(approved_at, created_at) asc') ->get() ->groupBy('step_index'); $formData = json_decode($instance->form_data, true) ?: []; $data = []; foreach ($steps as $i => $step) { $actions = ($records->get($i) ?? collect())->toArray(); if (!empty($actions)) { $cntApproved = 0; $cntRejected = 0; $cntPending = 0; foreach ($actions as $a) { if ($a->status === 'approved') $cntApproved++; elseif ($a->status === 'rejected') $cntRejected++; elseif ($a->status === 'pending') $cntPending++; // skipped/timeout 不阻断完结显示 } if ($cntRejected > 0) $status = 'rejected'; elseif ($cntPending > 0) $status = 'processing'; elseif ($cntApproved > 0) $status = 'approved'; else $status = 'approved'; // 全是 skipped/timeout 也视为已过 } else { if ($i < (int)$instance->current_step) { if (isset($step['condition']) && !$this->evaluateCondition($step['condition'], $formData)) { $status = 'skipped'; } else { $status = 'approved'; } } elseif ($i === (int)$instance->current_step && $instance->status === 'processing') { $status = 'processing'; } else { $status = 'pending'; } } $data[] = [ 'index' => $i, 'name' => $step['name'] ?? ('Step '.$i), 'type' => $step['type'] ?? 'all', 'status' => $status, 'actions' => $actions, ]; } return $data; } /** 查询某个审批实例的审批记录 */ public function getInstanceRecords(int $instanceId, ?string $userNameColumn = null): array { // 1) 实例 + 流程步骤 $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) ?: []; // 2) 记录查询(可选带出审批人姓名) if ($userNameColumn) { // 注意:确保 user 表存在该列 $records = Db::table('approval_records as ar') ->leftJoin('user as u', 'ar.approver_id', '=', 'u.id') ->where('ar.instance_id', $instanceId) ->orderBy('ar.step_index') ->orderByRaw('COALESCE(ar.approved_at, ar.created_at) asc') ->select( 'ar.*', Db::raw("u.`{$userNameColumn}` as approver_name") ) ->get() ->toArray(); } else { $records = Db::table('approval_records as ar') ->where('ar.instance_id', $instanceId) ->orderBy('ar.step_index') ->orderByRaw('COALESCE(ar.approved_at, ar.created_at) asc') ->select('ar.*') ->get() ->toArray(); } // 3) 附加步骤名/类型;统计汇总 $summary = ['approved'=>0,'rejected'=>0,'pending'=>0,'skipped'=>0,'timeout'=>0,'total'=>0]; foreach ($records as &$r) { $idx = (int)$r->step_index; $meta = $steps[$idx] ?? []; $r->step_name = $meta['name'] ?? ('Step '.$idx); $r->step_type = $meta['type'] ?? 'all'; if (isset($summary[$r->status])) { $summary[$r->status]++; } $summary['total']++; } unset($r); return [ 'instance' => [ 'id' => (int)$instance->id, 'flow_type' => $instance->flow_type, 'biz_type' => $instance->biz_type, 'biz_id' => $instance->biz_id, 'status' => $instance->status, 'current_step' => (int)$instance->current_step, 'created_at' => $instance->created_at, 'updated_at' => $instance->updated_at, 'finished_at' => $instance->finished_at, ], 'steps' => array_map(function($s){ return [ 'name' => $s['name'] ?? '', 'type' => $s['type'] ?? 'all', 'condition' => $s['condition'] ?? null, ]; }, $steps), 'records' => array_map(function($r){ // 转成数组(Illuminate 返回的是 stdClass) return [ 'id' => (int)$r->id, 'instance_id' => (int)$r->instance_id, 'approver_id' => (int)$r->approver_id, 'step_index' => (int)$r->step_index, 'action' => $r->action, 'status' => $r->status, 'comment' => $r->comment, 'approved_at' => $r->approved_at, 'created_at' => $r->created_at, 'updated_at' => $r->updated_at, 'approver_name'=> property_exists($r, 'approver_name') ? $r->approver_name : null, 'step_name' => $r->step_name, 'step_type' => $r->step_type, ]; }, $records), 'summary' => $summary, ]; } /** 简单时间线 */ public function getSimpleTimeline(int $instanceId, ?string $userNameColumn = null, string $initiatorPlacement = 'prepend'): array { // 基础数据 $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) ?: []; $formData = json_decode($instance->form_data ?? '{}', true) ?: []; $curStep = (int)$instance->current_step; $round = (int)$instance->round; // 申请人 $initiatorId = (int)$instance->initiator_id; $initiatorName = null; if ($userNameColumn) { $initiatorName = Db::table('user')->where('id', $initiatorId)->value($userNameColumn); } // 审批记录(仅当前轮次) $records = Db::table('approval_records as ar') ->where('ar.instance_id', $instanceId) ->where('ar.round', $round) ->orderBy('ar.step_index') ->orderByRaw('COALESCE(ar.approved_at, ar.created_at) asc') ->select('ar.*') ->get() ->toArray(); // 一次性拉取姓名,避免 N+1 $nameMap = []; if ($userNameColumn && !empty($records)) { $uids = []; foreach ($records as $r) $uids[] = (int)$r->approver_id; $uids = array_values(array_unique($uids)); if (!empty($uids)) { $pairs = Db::table('user')->whereIn('id', $uids)->pluck($userNameColumn, 'id')->toArray(); foreach ($pairs as $uid => $name) $nameMap[(int)$uid] = $name; } } // 记录分组 $byStep = []; foreach ($records as $r) { $idx = (int)$r->step_index; $byStep[$idx][] = $r; } $timeline = []; // 可选:发起申请块 if ($initiatorPlacement === 'prepend') { $appliedAt = $instance->created_at ?: null; $timeline[] = [ 'index' => -1, 'name' => '发起申请', 'type' => '-', 'status' => 'submitted', 'started_at' => $appliedAt, 'finished_at' => $appliedAt, 'duration_seconds' => 0, 'initiator_id' => $initiatorId, 'initiator_name' => $initiatorName, ]; } // 每一步 foreach ($steps as $i => $meta) { $name = $meta['name'] ?? ('Step '.$i); $typeRaw = strtolower($meta['type'] ?? 'all'); $type = in_array($typeRaw, ['any','all']) ? $typeRaw : 'all'; $condStr = $meta['condition'] ?? null; $condOk = $condStr ? $this->evaluateCondition($condStr, $formData) : true; $recs = $byStep[$i] ?? []; $hasRecs = !empty($recs); $status = 'pending'; $startedAt = null; $finishedAt = null; $durationSec = null; // 每一步的“审批意见”(只展示有内容的 comment) $opinions = []; // [{approver_id, approver_name?, action, status, comment, time}...] if ($hasRecs) { // 统计时间 $minCreated = null; $minApproveAt = null; $maxApproveAt = null; $minRejectAt = null; $maxTimeoutAt = null; $cnt = ['pending'=>0,'approved'=>0,'rejected'=>0,'timeout'=>0]; foreach ($recs as $rr) { // 收集“审批意见”(非空 comment 都算) $cmt = trim((string)($rr->comment ?? '')); if ($cmt !== '') { $opinions[] = [ 'approver_id' => (int)$rr->approver_id, 'approver_name' => $userNameColumn ? ($nameMap[(int)$rr->approver_id] ?? null) : null, 'action' => $rr->action, // approve | reject | null 'status' => $rr->status, // approved|rejected|pending|skipped|timeout 'comment' => $cmt, 'time' => $rr->approved_at ?: $rr->created_at, ]; } if ($rr->created_at && (!$minCreated || $rr->created_at < $minCreated)) $minCreated = $rr->created_at; if ($rr->approved_at) { if ($rr->status === 'approved') { if (!$minApproveAt || $rr->approved_at < $minApproveAt) $minApproveAt = $rr->approved_at; if (!$maxApproveAt || $rr->approved_at > $maxApproveAt) $maxApproveAt = $rr->approved_at; } elseif ($rr->status === 'rejected') { if (!$minRejectAt || $rr->approved_at < $minRejectAt) $minRejectAt = $rr->approved_at; } elseif ($rr->status === 'timeout') { if (!$maxTimeoutAt || $rr->approved_at > $maxTimeoutAt) $maxTimeoutAt = $rr->approved_at; } } if (isset($cnt[$rr->status])) $cnt[$rr->status]++; } // 时间升序排序 opinions if (!empty($opinions)) { usort($opinions, function($a, $b) { return strcmp($a['time'] ?? '', $b['time'] ?? ''); }); } $startedAt = $minCreated; // 总结果(注意:这里按你现在的签名调用,含 round) $outcome = $this->getStepOutcome($instanceId, $round, $i, $type); // 'pending'|'approve'|'reject' if ($outcome === 'reject') { $status = 'rejected'; $finishedAt = $minRejectAt; } elseif ($outcome === 'approve') { $status = 'approved'; $finishedAt = ($type === 'any') ? $minApproveAt : $maxApproveAt; } else { if ($cnt['timeout'] > 0 && $cnt['approved'] === 0 && $cnt['rejected'] === 0 && $cnt['pending'] === 0) { $status = 'timeout'; $finishedAt = $maxTimeoutAt; } else { $status = 'processing'; } } if ($startedAt && $finishedAt) { $durationSec = max(0, strtotime($finishedAt) - strtotime($startedAt)); } } else { // 无记录:根据位置与条件展示 if ($i < $curStep) { $status = $condOk ? 'approved' : 'skipped'; } elseif ($i === $curStep && $instance->status === 'processing') { $status = 'processing'; } else { $status = $condOk ? 'pending' : 'skipped'; } } $row = [ 'index' => (int)$i, 'name' => $name, 'type' => $type, 'status' => $status, 'started_at' => $startedAt, 'finished_at' => $finishedAt, 'duration_seconds' => $durationSec, 'opinions' => $opinions, // 这就是每个审批意见 ]; // 把申请人信息并到第一步(可选) if ($initiatorPlacement === 'attach' && empty($timeline)) { $row['initiator_id'] = $initiatorId; $row['initiator_name'] = $initiatorName; } $timeline[] = $row; } return $timeline; } /** 按业务ID获取“最新进展”的极简状态 */ public function getBriefStatusByBiz(string $bizId, ?string $bizType = null, ?string $userNameColumn = null): array { // 1) 最新实例 $q = Db::table('approval_instances')->where('biz_id', $bizId); if ($bizType !== null && $bizType !== '') { $q->where('biz_type', $bizType); } $instance = $q->orderByDesc('id')->first(); if (!$instance) { // throw new BusinessException('未找到对应的审批实例'); return []; } $base = [ 'instance_id' => (int)$instance->id, 'flow_type' => $instance->flow_type, 'biz_type' => $instance->biz_type, 'biz_id' => $instance->biz_id, 'status' => $instance->status, 'finished_at' => $instance->finished_at, 'updated_at' => $instance->updated_at, ]; // 2) 已完结:直接返回 if ($instance->status !== 'processing') { return $base; } // 3) 进行中:取当前步骤 + 当前 pending 审批者 $flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first(); if (!$flow) { // 容错:没有流程定义 return $base; } $steps = json_decode($flow->steps, true) ?: []; $idx = (int)$instance->current_step; $round = (int)$instance->round; $meta = $steps[$idx] ?? []; $typeRaw = strtolower($meta['type'] ?? 'all'); $type = in_array($typeRaw, ['any','all']) ? $typeRaw : 'all'; // 当前步的所有记录 $recsQ = Db::table('approval_records')->where('instance_id', $instance->id)->where('step_index', $idx); // 待处理人(只列出 pending) if ($userNameColumn) { $pendingRows = Db::table('approval_records as ar') ->leftJoin('user as u', 'ar.approver_id', '=', 'u.id') ->where('ar.instance_id', $instance->id) ->where('ar.round', $round) ->where('ar.step_index', $idx) ->where('ar.status', 'pending') ->select('ar.approver_id', Db::raw("u.`{$userNameColumn}` as approver_name")) ->get() ->toArray(); } else { $pendingRows = Db::table('approval_records') ->where('instance_id', $instance->id) ->where('round', $round) ->where('step_index', $idx) ->where('status', 'pending') ->select('approver_id') ->get() ->toArray(); } $pendingApprovers = []; foreach ($pendingRows as $r) { $row = ['approver_id' => (int)$r->approver_id]; if ($userNameColumn && property_exists($r, 'approver_name')) { $row['approver_name'] = $r->approver_name; } $pendingApprovers[] = $row; } // 当前步的聚合状态(进行中通常返回 processing;若全被超时任务置为 timeout,则返回 timeout) // 先快速统计是否纯 timeout $stats = Db::table('approval_records') ->where('instance_id', $instance->id) ->where('round', $round) ->where('step_index', $idx) ->selectRaw("SUM(CASE WHEN status='pending' THEN 1 ELSE 0 END) as c_pending") ->selectRaw("SUM(CASE WHEN status='approved' THEN 1 ELSE 0 END) as c_approved") ->selectRaw("SUM(CASE WHEN status='rejected' THEN 1 ELSE 0 END) as c_rejected") ->selectRaw("SUM(CASE WHEN status='timeout' THEN 1 ELSE 0 END) as c_timeout") ->first(); $stepStatus = 'processing'; if ($stats && (int)$stats->c_timeout > 0 && (int)$stats->c_pending === 0 && (int)$stats->c_approved === 0 && (int)$stats->c_rejected === 0) { $stepStatus = 'timeout'; } $base['step'] = [ 'index' => $idx, 'name' => $meta['name'] ?? ('Step '.$idx), 'type' => $type, // any|all 'status' => $stepStatus, // processing|timeout 'pending_approvers' => $pendingApprovers, // 当前正在等待的审批者 ]; return $base; } /** 登录/首屏初始化:获取当前用户的待办总数 */ public function getInitTodoCount(int $userId): int { $userId = (int)$userId; if ($userId <= 0) { throw new BusinessException('无效的用户ID'); } $row = Db::table('approval_records as ar') ->join('approval_instances as ai', 'ai.id', '=', 'ar.instance_id') ->where('ar.approver_id', $userId) ->where('ar.status', 'pending') ->where('ai.status', 'processing') // 只统计实例“当前轮次、当前步骤”的待办,避免历史残留干扰 ->whereRaw('ar.round = ai.round') ->whereRaw('ar.step_index = ai.current_step') ->selectRaw('COUNT(*) as c') ->first(); return (int)($row->c ?? 0); } }