get('page', 1); $offset = ($page - 1) * $pageSize; // 1. Получаем только ID и минимальные данные для пагинации $idQuery = $this->buildPaginatedIdQuery($searchText, $dateFrom, $dateTo, $status, $pageSize, $offset); $idResults = DB::select($idQuery['sql'], $idQuery['params']); if (empty($idResults)) { return new LengthAwarePaginator( collect(), 0, $pageSize, $page, ['path' => request()->url(), 'query' => request()->query()] ); } // 2. Группируем ID по типу (в архиве/не в архиве) $archiveIds = []; $misIds = []; foreach ($idResults as $row) { if ($row->in_archive) { $archiveIds[] = $row->id; } else { $misIds[] = $row->id; } } // 3. Получаем полные данные отдельными запросами (это быстрее чем UNION) $results = []; if (!empty($archiveIds)) { $archiveData = $this->getArchiveDataByIds($archiveIds); $results = array_merge($results, $archiveData); } if (!empty($misIds)) { $misData = $this->getMisDataByIds($misIds); $results = array_merge($results, $misData); } // 4. Сортируем как в ID запросе (сначала в архиве) usort($results, function($a, $b) { if ($a['in_archive'] == $b['in_archive']) { return 0; } return $a['in_archive'] ? -1 : 1; }); // 5. Получаем общее количество $total = $this->getTotalCount($searchText, $dateFrom, $dateTo, $status); return new LengthAwarePaginator( $results, $total, $pageSize, $page, ['path' => request()->url(), 'query' => request()->query()] ); } /** * Запрос для получения ID с пагинацией */ private function buildPaginatedIdQuery( ?string $searchText, ?string $dateFrom, ?string $dateTo, ?int $status, int $limit, int $offset ): array { $params = []; // 1. Архивные записи - используем EXISTS для проверки связей $archiveSql = " SELECT ai.id, ai.mis_history_id as original_id, true as in_archive, ai.created_at, 1 as priority FROM archive_infos ai WHERE 1=1 "; $archiveConditions = []; // Фильтр по статусу (быстрый) if ($status !== null && $status !== 0) { $archiveConditions[] = "ai.status_id = ?"; $params[] = $status; } // Поиск по тексту if ($searchText) { if (is_numeric($searchText)) { // Быстрый поиск по номерам $archiveConditions[] = "(ai.mis_num ILIKE ? OR ai.foxpro_num ILIKE ? OR ai.archive_num ILIKE ?)"; $params[] = $searchText . '%'; $params[] = $searchText . '%'; $params[] = $searchText . '%'; } else { // Поиск по ФИО - используем EXISTS для foreign table $words = preg_split('/\s+/', trim($searchText)); $words = array_filter($words); if (!empty($words)) { if (count($words) === 1) { $word = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $archiveConditions[] = "( EXISTS ( SELECT 1 FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id AND (mh.\"FAMILY\" ILIKE ? AND (mh.\"Name\" ILIKE ? OR mh.\"OT\" ILIKE ?)) ) OR EXISTS ( SELECT 1 FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id AND (fp.fam ILIKE ? OR fp.im ILIKE ? OR fp.ot ILIKE ?) ) )"; $params = array_merge($params, array_fill(0, 6, $word . '%')); } elseif (count($words) === 2) { $family = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $name = mb_strtoupper(mb_substr($words[1], 0, 1)) . mb_substr($words[1], 1); $archiveConditions[] = "( EXISTS ( SELECT 1 FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id AND mh.\"FAMILY\" ILIKE ? AND mh.\"Name\" ILIKE ? ) OR EXISTS ( SELECT 1 FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id AND fp.fam ILIKE ? AND fp.im ILIKE ? ) )"; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $family . '%'; $params[] = $name . '%'; } elseif (count($words) === 3) { $family = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $name = mb_strtoupper(mb_substr($words[1], 0, 1)) . mb_substr($words[1], 1); $ot = mb_strtoupper(mb_substr($words[2], 0, 1)) . mb_substr($words[2], 1); $archiveConditions[] = "( EXISTS ( SELECT 1 FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id AND mh.\"FAMILY\" ILIKE ? AND mh.\"Name\" ILIKE ? AND mh.\"OT\" ILIKE ? ) OR EXISTS ( SELECT 1 FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id AND fp.fam ILIKE ? AND fp.im ILIKE ? AND fp.ot ILIKE ? ) )"; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $ot . '%'; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $ot . '%'; } } } } // Фильтр по дате if ($dateFrom || $dateTo) { $dateConditions = []; $dateParams = []; if ($dateFrom) { $dateConditions[] = "COALESCE( (SELECT mh.\"DateExtract\" FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id), (SELECT fp.menddate FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id), ai.created_at ) >= ?"; $dateParams[] = $dateFrom; } if ($dateTo) { $dateConditions[] = "COALESCE( (SELECT mh.\"DateExtract\" FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id), (SELECT fp.menddate FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id), ai.created_at ) <= ?"; $dateParams[] = $dateTo; } if (!empty($dateConditions)) { $archiveConditions[] = "(" . implode(' AND ', $dateConditions) . ")"; $params = array_merge($params, $dateParams); } } if (!empty($archiveConditions)) { $archiveSql .= " AND " . implode(' AND ', $archiveConditions); } // 2. MIS записи не в архиве $misSql = " SELECT mh.\"MedicalHistoryID\" as id, mh.\"MedicalHistoryID\" as original_id, false as in_archive, mh.\"DateExtract\" as created_at, 2 as priority FROM stt_medicalhistory mh WHERE NOT EXISTS ( SELECT 1 FROM archive_infos ai WHERE ai.mis_history_id = mh.\"MedicalHistoryID\" ) AND mh.\"DateExtract\" > CAST('01-01-1900' as date) "; $misConditions = $this->buildMisConditions($searchText, $status); if (!empty($misConditions['conditions'])) { $misSql .= " AND " . implode(' AND ', $misConditions['conditions']); } $misParams = $misConditions['params']; // Объединяем параметки $allParams = array_merge($params, $misParams, [$limit, $offset]); $sql = " SELECT * FROM ( {$archiveSql} UNION ALL {$misSql} ) as combined ORDER BY priority, created_at DESC LIMIT ? OFFSET ? "; return ['sql' => $sql, 'params' => $allParams]; } private function buildMisConditions( ?string $searchText, ?int $status ): array { $conditions = []; $params = []; // Только если статус 0 или null показываем записи не в архиве if ($status !== null && $status !== 0) { return ['conditions' => ["1 = 0"], 'params' => []]; } // Поиск по тексту if ($searchText && !is_numeric($searchText)) { $words = preg_split('/\s+/', trim($searchText)); $words = array_filter($words); if (!empty($words)) { if (count($words) === 1) { $word = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $conditions[] = "(mh.\"FAMILY\" ILIKE ? OR mh.\"Name\" ILIKE ? OR mh.\"OT\" ILIKE ?)"; $params = array_fill(0, 3, $word . '%'); } elseif (count($words) === 2) { $family = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $name = mb_strtoupper(mb_substr($words[1], 0, 1)) . mb_substr($words[1], 1); $conditions[] = "(mh.\"FAMILY\" ILIKE ? AND mh.\"Name\" ILIKE ?)"; $params[] = $family . '%'; $params[] = $name . '%'; } elseif (count($words) >= 3) { $family = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $name = mb_strtoupper(mb_substr($words[1], 0, 1)) . mb_substr($words[1], 1); $ot = mb_strtoupper(mb_substr($words[2], 0, 1)) . mb_substr($words[2], 1); $conditions[] = "(mh.\"FAMILY\" ILIKE ? AND mh.\"Name\" ILIKE ? AND mh.\"OT\" ILIKE ?)"; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $ot . '%'; } } } return [ 'conditions' => $conditions, 'params' => $params ]; } /** * Условия для MIS записей не в архиве */ private function buildMisWhere( ?string $searchText, ?int $status ): array { $wheres = ["NOT EXISTS (SELECT 1 FROM archive_infos ai WHERE ai.mis_history_id = mh.\"MedicalHistoryID\")"]; $params = []; // Только если статус 0 или null показываем записи не в архиве if ($status !== null && $status !== 0) { $wheres[] = "1 = 0"; } // Поиск по тексту if ($searchText) { $searchConditions = $this->parseSearchTextForMis($searchText, $params); if (!empty($searchConditions)) { $wheres[] = "(" . implode(' OR ', $searchConditions) . ")"; } } $whereClause = 'WHERE ' . implode(' AND ', $wheres); return [ 'where' => $whereClause, 'params' => $params ]; } /** * Парсинг поисковой строки для MIS таблицы */ private function parseSearchTextForMis(string $searchText, array &$params): array { $conditions = []; // Если строка содержит только цифры - это номер карты if (is_numeric($searchText)) { $conditions[] = 'mh."MedCardNum" ILIKE ?'; $params[] = $searchText . '%'; return $conditions; } // Разбиваем строку на слова $words = preg_split('/\s+/', trim($searchText)); $words = array_filter($words); if (empty($words)) { return $conditions; } // Если одно слово - ищем по всем полям if (count($words) === 1) { $word = $words[0]; $conditions[] = 'mh."MedCardNum" ILIKE ?'; $conditions[] = 'mh."FAMILY" ILIKE ?'; $conditions[] = 'mh."Name" ILIKE ?'; $conditions[] = 'mh."OT" ILIKE ?'; $params = array_merge($params, array_fill(0, 4, $word . '%')); } // Если два слова - фамилия и имя elseif (count($words) === 2) { $family = $words[0]; $name = $words[1]; $conditions[] = '(mh."FAMILY" ILIKE ? AND mh."Name" ILIKE ?)'; $params[] = $family . '%'; $params[] = $name . '%'; } // Если три слова - фамилия, имя и отчество elseif (count($words) >= 3) { $family = $words[0]; $name = $words[1]; $ot = $words[2]; $conditions[] = '(mh."FAMILY" ILIKE ? AND (mh."Name" ILIKE ? OR mh."OT" ILIKE ?))'; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $ot . '%'; } return $conditions; } /** * Получение данных архивных записей по ID */ private function getArchiveDataByIds(array $ids): array { if (empty($ids)) { return []; } $placeholders = implode(',', array_fill(0, count($ids), '?')); $sql = " SELECT ai.id, ai.mis_history_id, ai.foxpro_history_id, COALESCE(ai.mis_num, ai.foxpro_num) as card_number, ai.archive_num, ai.post_in, ai.status_id, ai.created_at, ai.updated_at, COALESCE(mh.\"FAMILY\", fp.fam) as family, COALESCE(mh.\"Name\", fp.im) as name, COALESCE(mh.\"OT\", fp.ot) as ot, CONCAT( COALESCE(mh.\"FAMILY\", fp.fam), ' ', COALESCE(mh.\"Name\", fp.im), ' ', COALESCE(mh.\"OT\", fp.ot) ) as full_name, COALESCE(mh.\"DateRecipient\", fp.mpostdate) as date_recipient, COALESCE(mh.\"DateExtract\", fp.menddate) as date_extract, COALESCE(mh.\"BD\", fp.dr) as birth_date, true as in_archive, false as is_temporary, 'archive' as source, CASE WHEN ai.mis_history_id IS NOT NULL THEN 'mis' ELSE 'foxpro' END as history_type, mh.\"MedCardNum\" as mis_card_number, mh.\"SS\" as snils, fp.nkarta as foxpro_card_number, fp.snils as foxpro_snils, fp.enp as enp FROM archive_infos ai LEFT JOIN stt_medicalhistory mh ON ai.mis_history_id = mh.\"MedicalHistoryID\" LEFT JOIN foxpro_original_patient fp ON ai.foxpro_history_id = fp.keykarta WHERE ai.id IN ({$placeholders}) "; $results = DB::select($sql, $ids); return array_map(function($item) { return $this->transformResult($item); }, $results); } /** * Получение данных MIS записей по ID */ private function getMisDataByIds(array $ids): array { if (empty($ids)) { return []; } $placeholders = implode(',', array_fill(0, count($ids), '?')); $sql = " SELECT mh.\"MedicalHistoryID\" as id, mh.\"MedicalHistoryID\" as mis_history_id, NULL as foxpro_history_id, mh.\"MedCardNum\" as card_number, NULL as archive_num, NULL as post_in, 0 as status_id, mh.\"DateExtract\" as created_at, NULL as updated_at, mh.\"FAMILY\" as family, mh.\"Name\" as name, mh.\"OT\" as ot, CONCAT(mh.\"FAMILY\", ' ', mh.\"Name\", ' ', COALESCE(mh.\"OT\", '')) as full_name, mh.\"DateRecipient\" as date_recipient, mh.\"DateExtract\" as date_extract, mh.\"BD\" as birth_date, false as in_archive, true as is_temporary, 'mis' as source, 'mis' as history_type, mh.\"MedCardNum\" as mis_card_number, mh.\"SS\" as snils, NULL as foxpro_card_number, NULL as foxpro_snils, NULL as enp FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" IN ({$placeholders}) AND mh.\"DateExtract\" > CAST('01-01-1900' AS DATE) "; $results = DB::select($sql, $ids); return array_map(function($item) { return $this->transformResult($item); }, $results); } /** * Получение общего количества записей */ private function getTotalCount( ?string $searchText, ?string $dateFrom, ?string $dateTo, ?int $status ): int { $archiveCount = $this->getArchiveCount($searchText, $dateFrom, $dateTo, $status); $misCount = $this->getMisCount($searchText, $status); return $archiveCount + $misCount; } /** * Количество архивных записей */ private function getArchiveCount( ?string $searchText, ?string $dateFrom, ?string $dateTo, ?int $status ): int { $params = []; $conditions = []; // Фильтр по статусу if ($status !== null && $status !== 0) { $conditions[] = "ai.status_id = ?"; $params[] = $status; } // Поиск по тексту if ($searchText) { if (is_numeric($searchText)) { $conditions[] = "(ai.mis_num ILIKE ? OR ai.foxpro_num ILIKE ? OR ai.archive_num ILIKE ?)"; $params[] = $searchText . '%'; $params[] = $searchText . '%'; $params[] = $searchText . '%'; } else { $words = preg_split('/\s+/', trim($searchText)); $words = array_filter($words); if (!empty($words)) { if (count($words) === 1) { $word = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $conditions[] = "( EXISTS ( SELECT 1 FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id AND (mh.\"FAMILY\" ILIKE ? OR mh.\"Name\" ILIKE ? OR mh.\"OT\" ILIKE ?) ) OR EXISTS ( SELECT 1 FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id AND (fp.fam ILIKE ? OR fp.im ILIKE ? OR fp.ot ILIKE ?) ) )"; $params = array_merge($params, array_fill(0, 6, $word . '%')); } elseif (count($words) === 2) { $family = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $name = mb_strtoupper(mb_substr($words[1], 0, 1)) . mb_substr($words[1], 1); $conditions[] = "( EXISTS ( SELECT 1 FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id AND mh.\"FAMILY\" ILIKE ? AND mh.\"Name\" ILIKE ? ) OR EXISTS ( SELECT 1 FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id AND fp.fam ILIKE ? AND fp.im ILIKE ? ) )"; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $family . '%'; $params[] = $name . '%'; } elseif (count($words) === 3) { $family = mb_strtoupper(mb_substr($words[0], 0, 1)) . mb_substr($words[0], 1); $name = mb_strtoupper(mb_substr($words[1], 0, 1)) . mb_substr($words[1], 1); $ot = mb_strtoupper(mb_substr($words[2], 0, 1)) . mb_substr($words[2], 1); $conditions[] = "( EXISTS ( SELECT 1 FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id AND mh.\"FAMILY\" ILIKE ? AND mh.\"Name\" ILIKE ? AND mh.\"OT\" ILIKE ? ) OR EXISTS ( SELECT 1 FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id AND fp.fam ILIKE ? AND fp.im ILIKE ? AND fp.ot ILIKE ? ) )"; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $ot . '%'; $params[] = $family . '%'; $params[] = $name . '%'; $params[] = $ot . '%'; } } } } // Фильтр по дате if ($dateFrom || $dateTo) { $dateConditions = []; if ($dateFrom) { $dateConditions[] = "COALESCE( (SELECT mh.\"DateExtract\" FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id), (SELECT fp.menddate FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id), ai.created_at ) >= ?"; $params[] = $dateFrom; } if ($dateTo) { $dateConditions[] = "COALESCE( (SELECT mh.\"DateExtract\" FROM stt_medicalhistory mh WHERE mh.\"MedicalHistoryID\" = ai.mis_history_id), (SELECT fp.menddate FROM foxpro_original_patient fp WHERE fp.keykarta = ai.foxpro_history_id), ai.created_at ) <= ?"; $params[] = $dateTo; } if (!empty($dateConditions)) { $conditions[] = "(" . implode(' AND ', $dateConditions) . ")"; } } $sql = "SELECT COUNT(*) as count FROM archive_infos ai"; if (!empty($conditions)) { $sql .= " WHERE " . implode(' AND ', $conditions); } $result = DB::selectOne($sql, $params); return (int) $result->count; } /** * Количество MIS записей не в архиве */ private function getMisCount( ?string $searchText, ?int $status ): int { $conditions = $this->buildMisConditions($searchText, $status); // Добавляем NOT EXISTS в начало $allConditions = array_merge( ["NOT EXISTS (SELECT 1 FROM archive_infos ai WHERE ai.mis_history_id = mh.\"MedicalHistoryID\")"], $conditions['conditions'], ["mh.\"DateExtract\" > CAST('01-01-1900' AS DATE)"] ); $sql = " SELECT COUNT(*) as count FROM stt_medicalhistory mh WHERE " . implode(' AND ', $allConditions); $result = DB::selectOne($sql, $conditions['params']); return (int) $result->count; } /** * Преобразование результата */ private function transformResult($item): array { return [ 'id' => $item->id, 'mis_history_id' => $item->mis_history_id, 'foxpro_history_id' => $item->foxpro_history_id, 'card_number' => $item->card_number, 'archive_num' => $item->archive_num, 'post_in' => $item->post_in, 'status_id' => (int)$item->status_id, 'full_name' => $item->full_name, 'family' => $item->family, 'name' => $item->name, 'ot' => $item->ot, 'date_recipient' => $item->date_recipient, 'date_extract' => $item->date_extract, 'birth_date' => $item->birth_date, 'created_at' => $item->created_at, 'updated_at' => $item->updated_at, 'in_archive' => (bool)$item->in_archive, 'is_temporary' => (bool)$item->is_temporary, 'source' => $item->source, 'history_type' => $item->history_type, 'snils' => $item->snils ?? $item->foxpro_snils, 'enp' => $item->enp, 'mis_card_number' => $item->mis_card_number, 'foxpro_card_number' => $item->foxpro_card_number, 'status_text' => $this->getStatusText((int)$item->status_id, (bool)$item->in_archive), 'can_be_issued' => $this->canBeIssued((int)$item->status_id, (bool)$item->in_archive), 'can_add_to_archive' => !(bool)$item->in_archive, 'row_class' => $this->getRowClass((int)$item->status_id, (bool)$item->in_archive), ]; } private function getStatusText(int $statusId, bool $inArchive): string { if (!$inArchive) { return 'Не в архиве'; } return match($statusId) { 1 => 'В архиве', 2 => 'Выдано', 3 => 'Утрачено', 4 => 'Списано', default => 'Неизвестно', }; } private function canBeIssued(int $statusId, bool $inArchive): bool { return $inArchive && $statusId === 1; } private function getRowClass(int $statusId, bool $inArchive): string { if (!$inArchive) { return 'table-warning'; } return match($statusId) { 1 => 'table-success', 2 => 'table-info', 3, 4 => 'table-danger', default => '', }; } /** * Быстрый поиск для автокомплита */ public function quickSearch(string $searchText, int $limit = 20): array { if (is_numeric($searchText)) { return $this->quickSearchByCardNumber($searchText, $limit); } return $this->quickSearchByName($searchText, $limit); } private function quickSearchByCardNumber(string $cardNumber, int $limit): array { $searchPattern = '%' . $cardNumber . '%'; $sql = " SELECT * FROM ( -- Архивные записи SELECT ai.id, COALESCE(ai.mis_num, ai.foxpro_num) as card_number, CONCAT( COALESCE(mh.\"FAMILY\", fp.fam), ' ', COALESCE(mh.\"Name\", fp.im) ) as full_name, true as in_archive, 'archive' as source FROM archive_infos ai LEFT JOIN stt_medicalhistory mh ON ai.mis_history_id = mh.\"MedicalHistoryID\" LEFT JOIN foxpro_original_patient fp ON ai.foxpro_history_id = fp.keykarta WHERE ai.mis_num ILIKE ? OR ai.foxpro_num ILIKE ? UNION ALL -- MIS записи не в архиве SELECT mh.\"MedicalHistoryID\" as id, mh.\"MedCardNum\" as card_number, CONCAT(mh.\"FAMILY\", ' ', mh.\"Name\") as full_name, false as in_archive, 'mis' as source FROM stt_medicalhistory mh WHERE NOT EXISTS ( SELECT 1 FROM archive_infos ai WHERE ai.mis_history_id = mh.\"MedicalHistoryID\" ) AND mh.\"MedCardNum\" ILIKE ? ) as results ORDER BY in_archive DESC, full_name LIMIT ? "; $params = [$searchPattern, $searchPattern, $searchPattern, $limit]; $results = DB::select($sql, $params); return array_map(function($item) { return [ 'id' => $item->id, 'card_number' => $item->card_number, 'full_name' => $item->full_name, 'in_archive' => (bool)$item->in_archive, 'source' => $item->source, 'can_be_issued' => (bool)$item->in_archive, ]; }, $results); } private function quickSearchByName(string $name, int $limit): array { $searchPattern = '%' . $name . '%'; $sql = " SELECT * FROM ( -- Архивные записи SELECT ai.id, COALESCE(ai.mis_num, ai.foxpro_num) as card_number, CONCAT( COALESCE(mh.\"FAMILY\", fp.fam), ' ', COALESCE(mh.\"Name\", fp.im) ) as full_name, true as in_archive, 'archive' as source FROM archive_infos ai LEFT JOIN stt_medicalhistory mh ON ai.mis_history_id = mh.\"MedicalHistoryID\" LEFT JOIN foxpro_original_patient fp ON ai.foxpro_history_id = fp.keykarta WHERE mh.\"FAMILY\" ILIKE ? OR mh.\"Name\" ILIKE ? UNION ALL -- MIS записи не в архиве SELECT mh.\"MedicalHistoryID\" as id, mh.\"MedCardNum\" as card_number, CONCAT(mh.\"FAMILY\", ' ', mh.\"Name\") as full_name, false as in_archive, 'mis' as source FROM stt_medicalhistory mh WHERE NOT EXISTS ( SELECT 1 FROM archive_infos ai WHERE ai.mis_history_id = mh.\"MedicalHistoryID\" ) AND (mh.\"FAMILY\" ILIKE ? OR mh.\"Name\" ILIKE ?) ) as results ORDER BY in_archive DESC, full_name LIMIT ? "; $params = [$searchPattern, $searchPattern, $searchPattern, $searchPattern, $limit]; $results = DB::select($sql, $params); return array_map(function($item) { return [ 'id' => $item->id, 'card_number' => $item->card_number, 'full_name' => $item->full_name, 'in_archive' => (bool)$item->in_archive, 'source' => $item->source, 'can_be_issued' => (bool)$item->in_archive, ]; }, $results); } /** * Поиск по точному номеру карты (для выдачи) */ public function searchByExactCardNumber(string $cardNumber): ?array { // Сначала ищем в архиве $sql = " SELECT ai.id, ai.mis_history_id, ai.foxpro_history_id, COALESCE(ai.mis_num, ai.foxpro_num) as card_number, COALESCE(mh.\"FAMILY\", fp.fam) as family, COALESCE(mh.\"Name\", fp.im) as name, COALESCE(mh.\"OT\", fp.ot) as ot, ai.archive_num, ai.post_in, ai.status_id, true as in_archive, 'archive' as source, ai.created_at, COALESCE(mh.\"DateRecipient\", fp.mpostdate) as date_recipient, COALESCE(mh.\"DateExtract\", fp.menddate) as date_extract FROM archive_infos ai LEFT JOIN stt_medicalhistory mh ON ai.mis_history_id = mh.\"MedicalHistoryID\" LEFT JOIN foxpro_original_patient fp ON ai.foxpro_history_id = fp.keykarta WHERE ai.mis_num = ? OR ai.foxpro_num = ? LIMIT 1 "; $result = DB::selectOne($sql, [$cardNumber, $cardNumber]); if ($result) { $result->full_name = trim($result->family . ' ' . $result->name . ' ' . ($result->ot ?? '')); $result->is_temporary = false; return $this->transformResult($result); } // Если нет в архиве, ищем в MIS $sql = " SELECT mh.\"MedicalHistoryID\" as id, mh.\"MedicalHistoryID\" as mis_history_id, NULL as foxpro_history_id, mh.\"MedCardNum\" as card_number, mh.\"FAMILY\" as family, mh.\"Name\" as name, mh.\"OT\" as ot, NULL as archive_num, NULL as post_in, 0 as status_id, false as in_archive, 'mis' as source, mh.\"DateExtract\" as created_at, mh.\"DateRecipient\" as date_recipient, mh.\"DateExtract\" as date_extract FROM stt_medicalhistory mh WHERE mh.\"MedCardNum\" = ? AND NOT EXISTS ( SELECT 1 FROM archive_infos ai WHERE ai.mis_history_id = mh.\"MedicalHistoryID\" ) LIMIT 1 "; $result = DB::selectOne($sql, [$cardNumber]); if ($result) { $result->full_name = trim($result->family . ' ' . $result->name . ' ' . ($result->ot ?? '')); $result->is_temporary = true; return $this->transformResult($result); } return null; } }