942 lines
35 KiB
PHP
942 lines
35 KiB
PHP
<?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;
|
||
}
|
||
}
|