Files
kartoteka/app/Repositories/MedicalHistoryRepository.php
2025-12-25 17:30:50 +09:00

942 lines
35 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Repositories;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
class MedicalHistoryRepository
{
/**
* Основной метод поиска через RAW SQL
*/
public function unifiedSearch(
?string $searchText,
?string $dateFrom,
?string $dateTo,
?int $status,
int $pageSize = 50
): LengthAwarePaginator
{
$page = request()->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\"
)
";
$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.\"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.\"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})
";
$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']
);
$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_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.\"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.\"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;
}
}