whereIn('id', $aids)->column('name', 'id'); $maxSlot = 4; foreach ($calc['ordersByAssistant'] as $bySlot) { foreach ($bySlot as $slot => $_) { $maxSlot = max($maxSlot, $slot); } } $slots = range(2, $maxSlot); $byDept = []; foreach ($aids as $aid) { $deptId = $ctx['assistantDept'][$aid]; $byDept[$deptId][] = $aid; } $rows = []; foreach ($byDept as $deptId => $deptAids) { $children = []; $deptName = (string) ($ctx['deptNames'][$deptId] ?? ('#' . $deptId)); foreach ($deptAids as $aid) { $children[] = [ 'row_key' => 'a' . $aid, 'is_dept' => 0, 'dept_id' => $deptId, 'dept_name' => $deptName, 'assistant_id' => $aid, 'assistant_name' => (string) ($names[$aid] ?? ('#' . $aid)), ] + RevisitCohortCalculator::metricPack($calc, [$aid], $slots); } usort($children, static fn (array $a, array $b): int => ($b['assigned_total_count'] <=> $a['assigned_total_count']) ?: strcmp($a['assistant_name'], $b['assistant_name'])); $rows[] = [ 'row_key' => 'd' . $deptId, 'is_dept' => 1, 'dept_id' => $deptId, 'dept_name' => $deptName, 'assistant_count' => \count($children), 'children' => $children, ] + RevisitCohortCalculator::metricPack($calc, $deptAids, $slots); } usort($rows, static fn (array $a, array $b): int => ($b['assigned_total_count'] <=> $a['assigned_total_count']) ?: strcmp($a['dept_name'], $b['dept_name'])); return self::metadata($ctx) + [ 'slots' => $slots, 'total' => RevisitCohortCalculator::metricPack($calc, $aids, $slots), 'rows' => $rows, ]; } public static function assignLines(array $params, string $type = RevisitCohortCalculator::TYPE_NEW): array { $ctx = self::cohortContext($params, $type); $aids = self::scopedAssistantIds($ctx, $params); $byDiag = []; foreach ($aids as $aid) { foreach ($ctx['calc']['pairsByAssistant'][$aid] ?? [] as $did => $pair) { if (!isset($byDiag[$did])) { $byDiag[$did] = $pair + ['assistants' => []]; $byDiag[$did]['assign_count'] = 0; } $byDiag[$did]['assistants'][$aid] = true; $byDiag[$did]['assign_count'] += $pair['assign_count']; $byDiag[$did]['first_assign_time'] = min($byDiag[$did]['first_assign_time'], $pair['first_assign_time']); $byDiag[$did]['last_assign_time'] = max($byDiag[$did]['last_assign_time'], $pair['last_assign_time']); $byDiag[$did]['is_rollback'] |= $pair['is_rollback']; } } $diagInfo = self::fetchDiagnosisInfo(array_keys($byDiag)); $names = $aids === [] ? [] : Db::name('admin')->whereIn('id', $aids)->column('name', 'id'); $rows = []; foreach ($byDiag as $did => $pair) { $assistantNames = []; foreach ($pair['assistants'] as $aid => $_) { $assistantNames[] = (string) ($names[$aid] ?? ('#' . $aid)); } $rows[] = [ 'diagnosis_id' => $did, 'patient_name' => (string) ($diagInfo[$did]['patient_name'] ?? ''), 'patient_phone' => (string) ($diagInfo[$did]['phone'] ?? ''), 'assistant_names' => implode('、', $assistantNames), 'assign_count' => $pair['assign_count'], 'first_assign_time_text' => date('Y-m-d H:i:s', $pair['first_assign_time']), 'last_assign_time' => $pair['last_assign_time'], 'last_assign_time_text' => date('Y-m-d H:i:s', $pair['last_assign_time']), 'assign_type' => $ctx['calc']['cohort_type'], 'is_rollback' => $pair['is_rollback'], ]; } usort($rows, static fn (array $a, array $b): int => $b['last_assign_time'] <=> $a['last_assign_time']); return self::metadata($ctx) + ['count' => \count($rows), 'rows' => $rows]; } public static function visitOrderLines(array $params, string $type = RevisitCohortCalculator::TYPE_NEW): array { $slot = (int) ($params['slot'] ?? 0); if ($slot < 0 || $slot === 1 || $slot > RevisitRateCalculator::MAX_VISIT_SLOT) { throw new \InvalidArgumentException('诊次须为 2~50,或 0(复诊合计)'); } $ctx = self::cohortContext($params, $type); $orders = RevisitCohortCalculator::scopedOrders($ctx['calc'], self::scopedAssistantIds($ctx, $params), $slot); $flat = []; $adminIds = []; $diagIds = []; foreach ($orders as $byDiag) { foreach ($byDiag as $did => $r) { $flat[] = $r; $diagIds[$did] = true; $adminIds[(int) $r['assistant_id']] = true; if ((int) ($r['creator_id'] ?? 0) > 0) { $adminIds[(int) $r['creator_id']] = true; } } } $diagInfo = self::fetchDiagnosisInfo(array_keys($diagIds)); $names = $adminIds === [] ? [] : Db::name('admin')->whereIn('id', array_keys($adminIds))->column('name', 'id'); $rows = []; foreach ($flat as $r) { $did = (int) $r['diagnosis_id']; $aid = (int) $r['assistant_id']; $cid = (int) ($r['creator_id'] ?? 0); $rows[] = [ 'order_id' => (int) $r['id'], 'order_no' => (string) ($r['order_no'] ?? ''), 'diagnosis_id' => $did, 'patient_name' => (string) ($diagInfo[$did]['patient_name'] ?? ''), 'patient_phone' => (string) ($diagInfo[$did]['phone'] ?? ''), 'amount' => round((float) ($r['amount'] ?? 0), 2), 'create_time' => (int) $r['create_time'], 'create_time_text' => date('Y-m-d H:i:s', (int) $r['create_time']), 'assistant_id' => $aid, 'assistant_name' => (string) ($names[$aid] ?? ('#' . $aid)), 'creator_name' => $cid > 0 ? (string) ($names[$cid] ?? ('#' . $cid)) : '—', 'visit_slot' => (int) $r['visit_slot'], 'global_visit_slot' => (int) $r['global_visit_slot'], 'cohort_assign_time_text' => date('Y-m-d H:i:s', (int) $r['cohort_assign_time']), ]; } usort($rows, static fn (array $a, array $b): int => $b['create_time'] <=> $a['create_time']); return self::metadata($ctx) + ['slot' => $slot, 'count' => \count($rows), 'rows' => $rows]; } /** 保留部门下拉结构,补充二中心内医助;不影响复诊到期页对父类 deptOptions 的复用。 */ public static function deptOptions(): array { $options = self::cohortDeptOptions(); $deptSet = self::resolveDeptFilterSet(null); if ($deptSet === []) { return $options + ['assistants' => []]; } $ids = Db::name('admin_dept')->whereIn('dept_id', array_keys($deptSet))->group('admin_id')->column('admin_id'); [$assistantDept] = self::buildAssistantDeptIndex($ids); $admins = $ids === [] ? [] : Db::name('admin')->whereIn('id', $ids)->whereNull('delete_time') ->field(['id', 'name'])->order('name', 'asc')->select()->toArray(); $assistants = []; foreach ($admins as $admin) { $deptId = (int) ($assistantDept[(int) $admin['id']] ?? 0); if (isset($deptSet[$deptId])) { $assistants[] = ['id' => (int) $admin['id'], 'name' => (string) $admin['name'], 'dept_id' => $deptId]; } } return $options + ['assistants' => $assistants]; } private static function cohortContext(array $params, string $type): array { $now = time(); [$start, $end, $startTs, $endTs] = RevisitCohortCalculator::dateRange($params, $now); // 先按指派周期圈定诊单,再读取其完整时间线;避免小范围查询拉取全库历史日志。 $candidateQuery = Db::name('tcm_diagnosis_assign_log')->alias('lg') ->join('tcm_diagnosis dg', 'dg.id = lg.diagnosis_id AND dg.delete_time IS NULL', 'INNER') ->where('lg.create_time', '>=', $startTs)->where('lg.create_time', '<=', min($endTs, $now)) ->where('lg.to_assistant_id', '>', 0)->where('lg.diagnosis_id', '>', 0); $filterAid = (int) ($params['assistant_id'] ?? 0); if ($filterAid > 0) { $candidateQuery->where('lg.to_assistant_id', $filterAid); } $candidateIds = array_map('intval', $candidateQuery->group('lg.diagnosis_id')->column('lg.diagnosis_id')); $logs = []; foreach (array_chunk($candidateIds, 2000) as $chunk) { $chunkLogs = Db::name('tcm_diagnosis_assign_log')->alias('lg') ->whereIn('lg.diagnosis_id', $chunk)->where('lg.create_time', '<=', $now) ->field(['lg.id', 'lg.diagnosis_id', 'lg.from_assistant_id', 'lg.to_assistant_id', 'lg.operator_admin_id', 'lg.create_time', 'lg.is_inherit']) ->order(['lg.diagnosis_id' => 'asc', 'lg.create_time' => 'asc', 'lg.id' => 'asc']) ->select()->toArray(); array_push($logs, ...$chunkLogs); } $calc = RevisitCohortCalculator::compute( $logs, self::iterateOrderSeqRows($candidateIds, ['o.id', 'o.order_no', 'o.diagnosis_id', 'o.create_time', 'o.amount', 'o.creator_id']), self::fetchRevisitSlotStartOffsetMap($candidateIds), self::fetchRefundOrRejectDiagnosisSet($candidateIds), self::fetchRollbackKeySet($startTs, min($endTs, $now)), $startTs, $endTs, $now, $type ); [$assistantDept, $deptNames] = self::buildAssistantDeptIndex(array_keys($calc['pairsByAssistant'])); $deptSet = self::resolveDeptFilterSet($params['dept_ids'] ?? null); foreach (array_keys($calc['pairsByAssistant']) as $aid) { if (!isset($deptSet[$assistantDept[$aid] ?? 0]) || ($filterAid > 0 && $filterAid !== $aid)) { unset($calc['pairsByAssistant'][$aid], $calc['ordersByAssistant'][$aid]); } } return ['calc' => $calc, 'assistantDept' => $assistantDept, 'deptNames' => $deptNames, 'start_date' => $start, 'end_date' => $end, 'observed_until' => date('Y-m-d H:i:s', $now)]; } private static function scopedAssistantIds(array $ctx, array $params): array { $aids = array_keys($ctx['calc']['pairsByAssistant']); $scopeAid = (int) ($params['scope_assistant_id'] ?? 0); if ($scopeAid > 0) { return \in_array($scopeAid, $aids, true) ? [$scopeAid] : []; } if (isset($params['dept_id']) && $params['dept_id'] !== '') { $deptId = (int) $params['dept_id']; $aids = array_values(array_filter($aids, static fn (int $aid): bool => ($ctx['assistantDept'][$aid] ?? 0) === $deptId)); } return $aids; } private static function metadata(array $ctx): array { return ['start_date' => $ctx['start_date'], 'end_date' => $ctx['end_date'], 'observed_until' => $ctx['observed_until'], 'cohort_type' => $ctx['calc']['cohort_type']]; } }