Новая логика поисковой выдачи

И добавлена кнопка Добавить карту в архив
This commit is contained in:
brusnitsyn
2025-12-26 17:41:25 +09:00
parent 329304076d
commit c5c1a2b3e1
14 changed files with 508 additions and 38 deletions

View File

@@ -38,7 +38,7 @@ class ArchiveHistoryController extends Controller
'employee_post' => 'nullable|string',
'comment' => 'nullable|string',
'has_lost' => 'boolean',
'archive_info_id' => 'required|numeric',
'history_id' => 'required',
'type' => 'required|string',
]);
@@ -55,9 +55,16 @@ class ArchiveHistoryController extends Controller
->format('Y-m-d');
}
$archiveInfo = ArchiveInfo::whereId($data['archive_info_id'])->first();
if ($data['type'] === 'mis') {
$archiveHistory = $archiveInfo->misHistory->archiveHistory()->create([
$patient = \App\Models\Mis\SttMedicalHistory::where('MedicalHistoryID', $data['history_id'])->first();
} else {
$patient = \App\Models\Si\SttMedicalHistory::where('keykarta', $data['history_id'])->first();
}
// $archiveInfo = ArchiveInfo::whereId($data['archive_info_id'])->first();
if ($data['type'] === 'mis') {
$archiveHistory = $patient->archiveHistory()->create([
'issue_at' => $data['issue_at'],
'return_at' => $data['return_at'],
'org_id' => $data['org_id'],
@@ -65,10 +72,10 @@ class ArchiveHistoryController extends Controller
'employee_post' => $data['employee_post'],
'comment' => $data['comment'],
'has_lost' => $data['has_lost'],
'mis_history_id' => $archiveInfo->mis_history_id
'mis_history_id' => $patient->MedicalHistoryID
]);
} else {
$archiveHistory = $archiveInfo->foxproHistory->archiveHistory()->create([
$archiveHistory = $patient->archiveHistory()->create([
'issue_at' => $data['issue_at'],
'return_at' => $data['return_at'],
'org_id' => $data['org_id'],
@@ -76,7 +83,7 @@ class ArchiveHistoryController extends Controller
'employee_post' => $data['employee_post'],
'comment' => $data['comment'],
'has_lost' => $data['has_lost'],
'foxpro_history_id' => $archiveInfo->foxpro_history_id,
'foxpro_history_id' => $patient->keykarta,
]);
}
@@ -131,10 +138,9 @@ class ArchiveHistoryController extends Controller
$data['post_in'] = Carbon::createFromTimestampMs($data['post_in'])->format('Y-m-d');
}
$archiveInfo = ArchiveInfo::whereId($patientId)->first();
$archiveInfo = ArchiveInfo::whereId($data['id'])->first();
$archiveInfo->updateOrCreate(
['id' => $patientId],
$archiveInfo->update(
[
'archive_num' => $data['num'],
'post_in' => $data['post_in'],

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers;
use App\Models\ArchiveInfo;
use App\Models\Mis\SttMedicalHistory;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
class ArchiveInfoController extends Controller
{
public function store(Request $request)
{
$data = $request->validate([
'id' => 'required',
'num' => 'required',
'post_in' => 'required'
]);
// Преобразуем timestamp в дату, если пришли числа
if (isset($data['post_in']) && is_numeric($data['post_in'])) {
$data['post_in'] = Carbon::createFromTimestampMs($data['post_in'])
->setTimezone(config('app.timezone'))
->format('Y-m-d');
}
$history = SttMedicalHistory::where('MedicalHistoryID', $data['id'])->first();
$hasCreated = ArchiveInfo::create([
'mis_num' => $history->MedCardNum,
'mis_history_id' => $data['id'],
'archive_num' => $data['num'],
'post_in' => $data['post_in'],
]);
return $hasCreated;
}
}

View File

@@ -2,21 +2,25 @@
namespace App\Http\Controllers;
use App\Http\Resources\IndexSttMedicalHistoryResource;
use App\Http\Resources\SI\SttMedicalHistoryResource as SiSttMedicalHistoryResource;
use App\Http\Resources\Mis\SttMedicalHistoryResource as MisSttMedicalHistoryResource;
use App\Models\ArchiveStatus;
use App\Models\SI\SttMedicalHistory;
use App\Repositories\MedicalHistoryRepository;
use App\Services\ArchiveCardService;
use Illuminate\Http\Request;
use Inertia\Inertia;
class IndexController extends Controller
{
private MedicalHistoryRepository $repository;
private ArchiveCardService $cardService;
public function __construct(MedicalHistoryRepository $repository)
public function __construct(MedicalHistoryRepository $repository, ArchiveCardService $cardService)
{
$this->repository = $repository;
$this->cardService = $cardService;
}
public function index(Request $request)
@@ -27,7 +31,15 @@ class IndexController extends Controller
$dateExtractTo = $request->get('date_extract_to', null);
$status = $request->get('status', null);
$data = $this->repository->unifiedSearch(
// $data = $this->repository->unifiedSearch(
// $searchText,
// $dateExtractFrom,
// $dateExtractTo,
// $status,
// $pageSize
// );
$data = $this->cardService->get(
$searchText,
$dateExtractFrom,
$dateExtractTo,
@@ -35,6 +47,8 @@ class IndexController extends Controller
$pageSize
);
// dd($data);
$statuses = ArchiveStatus::all()->map(function ($status) {
return [
'value' => $status->id,
@@ -42,13 +56,8 @@ class IndexController extends Controller
];
});
$statuses->push([
'value' => 0,
'label' => 'Нет в архиве',
]);
return Inertia::render('Home/Index', [
'cards' => MisSttMedicalHistoryResource::collection($data),
'cards' => IndexSttMedicalHistoryResource::collection($data),
'statuses' => $statuses,
'filters' => array_merge($request->only([
'search', 'date_extract_from', 'date_extract_to',

View File

@@ -33,7 +33,7 @@ class MedicalHistoryController extends Controller
'can_be_issued' => $patient->canBeIssued()
],
'journal' => $archiveJournal,
'archiveInfo' => $archiveInfo
'archiveInfo' => $archiveInfo ? ArchiveInfoResource::make($archiveInfo) : null
];
return response()->json($patientInfo);

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers;
use App\Models\Mis\SttMedicalHistory;
use Illuminate\Http\Request;
class MisHistoryController extends Controller
{
public function search(Request $request)
{
$data = $request->validate([
'query' => 'required|string'
]);
$searchText = $data['query'];
$searchParts = preg_split('/\s+/', trim($searchText));
$query = SttMedicalHistory::query();
// Проверяем, начинается ли строка с цифры (вероятно, это номер карты)
$isCardNumberSearch = is_numeric($searchParts[0]);
if ($isCardNumberSearch) {
// Паттерн: № Ф И О
$query->where('MedCardNum', 'ILIKE', "$searchParts[0]%");
// ФИО начинается со второй части
$fioStartIndex = 1;
} else {
// Паттерн: Ф И О (без номера)
$fioStartIndex = 0;
}
// Ищем ФИО в зависимости от количества оставшихся частей
$fioPartsCount = count($searchParts) - $fioStartIndex;
if ($fioPartsCount === 1) {
// Одно слово - ищем в фамилии, имени или отчестве
$query->where(function ($q) use ($searchParts, $fioStartIndex) {
$q->where('FAMILY', 'ILIKE', "{$searchParts[$fioStartIndex]}%")
->orWhere('Name', 'ILIKE', "{$searchParts[$fioStartIndex]}%")
->orWhere('OT', 'ILIKE', "{$searchParts[$fioStartIndex]}%");
});
} elseif ($fioPartsCount === 2) {
// Два слова - фамилия и инициал имени
$query->where(function ($q) use ($searchParts, $fioStartIndex) {
$q->where('FAMILY', 'ILIKE', "{$searchParts[$fioStartIndex]}%")
->where('Name', 'ILIKE', "{$searchParts[$fioStartIndex + 1]}%");
});
} elseif ($fioPartsCount === 3) {
// Три слова - полное ФИО
$query->where(function ($q) use ($searchParts, $fioStartIndex) {
$q->where('FAMILY', 'ILIKE', "{$searchParts[$fioStartIndex]}%")
->where('Name', 'ILIKE', "{$searchParts[$fioStartIndex + 1]}%")
->where('OT', 'ILIKE', "{$searchParts[$fioStartIndex + 2]}%");
});
}
// Также ищем полную строку в объединенных полях
$query->orWhereRaw("CONCAT(\"MedCardNum\", ' ', \"FAMILY\", ' ', \"Name\", ' ', \"OT\") ILIKE ?", ["{$searchText}%"]);
$results = $query->select([
'MedicalHistoryID', 'MedCardNum', 'FAMILY', 'Name', 'OT'
])->get()->map(function ($item) {
return [
'label' => "$item->MedCardNum - $item->FAMILY $item->Name $item->OT",
'value' => $item->MedicalHistoryID
];
});
return response()->json($results);
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Http\Resources;
use App\Models\ArchiveStatus;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class IndexSttMedicalHistoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
// Получаем оригинальную модель
$model = $this->resource;
$archiveInfo = $model->archiveInfo;
$historyType = $archiveInfo->historyType();
$inArchive = $archiveInfo->exists;
$hasMisType = $historyType === 'mis';
$family = $hasMisType ? $model->FAMILY : $model->fam;
$im = $hasMisType ? $this->Name : $this->im;
$ot = $hasMisType ? $this->OT : $this->ot;
$fio = "$family $im $ot";
$birthDate = $hasMisType ? $this->BD : $this->dr;
$dateRecipient = $hasMisType ? $this->DateRecipient : $this->mpostdate;
$dateExtract = $hasMisType ? $this->DateExtract : $this->menddate;
$postIn = $inArchive ? $archiveInfo->post_in : null;
$formattedBirthDate = $birthDate ? Carbon::parse($birthDate)->format('d.m.Y') : null;
$formattedDateRecipient = $dateRecipient ? Carbon::parse($dateRecipient)->format('d.m.Y') : null;
$formattedDateExtract = $dateExtract ? Carbon::parse($dateExtract)->format('d.m.Y') : null;
$formattedPostIn = $postIn ? Carbon::parse($postIn)->format('d.m.Y') : null;
$id = $historyType === 'mis' ? $this->MedicalHistoryID : $this->keykarta;
$archiveNum = $inArchive ? $archiveInfo->archive_num : null;
$status = $inArchive ? $archiveInfo->status : ArchiveStatus::find(5)->first();
$misCardNumber = $this->MedCardNum;
$foxproCardNumber = $this->nkarta;
$archiveInfoMisCardNumber = $inArchive ? $archiveInfo->mis_num : null;
$archiveInfoFoxproCardNumber = $inArchive ? $archiveInfo->foxpro_num : null;
$hasDividerCardNumber = isset($archiveInfoMisCardNumber) && isset($archiveInfoFoxproCardNumber);
$cardNumber = $hasDividerCardNumber
? "$archiveInfoMisCardNumber / $archiveInfoFoxproCardNumber"
: $archiveInfoMisCardNumber ?? $archiveInfoFoxproCardNumber;
return [
'id' => $id,
'history_type' => $historyType,
// Основные данные
'fullname' => $fio,
'family' => $family,
'name' => $im,
'ot' => $ot,
'dr' => $formattedBirthDate,
'daterecipient' => $formattedDateRecipient,
'dateextract' => $formattedDateExtract,
// Номера карт
'medcardnum' => $cardNumber, // MIS номер или FoxPro номер
'card_num' => $archiveNum, // Архивный номер
'datearhiv' => $formattedPostIn,
// Статус и возможности
'status' => $status,
'status_id' => $this->resource['status_id'] ?? 0,
'can_be_issued' => $this->resource['can_be_issued'] ?? false,
'can_add_to_archive' => $this->resource['can_add_to_archive'] ?? false,
];
}
}

View File

@@ -23,16 +23,12 @@ class ArchiveHistory extends Model
public function updateArchiveInfoStatus()
{
// Получаем связанную модель через морф
$historyable = $this->historyable;
if (!$historyable) {
return;
}
// Проверяем, есть ли у модели архивная информация
if (method_exists($historyable, 'archiveInfo') && $historyable->archiveInfo) {
$historyable->archiveInfo->update([
if ($this->mis_history_id) {
$this->misHistory->archiveInfo->update([
'status_id' => $this->determineStatusId()
]);
} else {
$this->foxproHistory->archiveInfo->update([
'status_id' => $this->determineStatusId()
]);
}
@@ -70,12 +66,12 @@ class ArchiveHistory extends Model
public function foxproHistory()
{
return $this->belongsTo(SiMedicalHistory::class, 'keykarta', 'foxpro_history_id');
return $this->belongsTo(SiMedicalHistory::class, 'foxpro_history_id', 'keykarta');
}
public function misHistory()
{
return $this->belongsTo(MisMedicalHistory::class, 'MedicalHistoryID', 'mis_history_id');
return $this->belongsTo(MisMedicalHistory::class, 'mis_history_id', 'MedicalHistoryID');
}
public function historyType()

View File

@@ -0,0 +1,140 @@
<?php
namespace App\Services;
use App\Models\ArchiveInfo;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class ArchiveCardService
{
public function get(?string $searchText,
?string $dateExtractFrom,
?string $dateExtractTo,
?int $status,
string $pageSize
) : LengthAwarePaginator
{
$query = ArchiveInfo::with(['misHistory', 'foxproHistory'])
->orderBy('post_in', 'desc');
// Поиск по тексту (если передан)
if (!empty($searchText)) {
$query->where(function ($q) use ($searchText) {
// Разбиваем строку поиска на части (по пробелам)
$searchParts = preg_split('/\s+/', trim($searchText));
$q->where(function ($subQuery) use ($searchParts, $searchText) {
// Сначала проверяем MIS историю
$subQuery->whereHas('misHistory', function ($misQuery) use ($searchParts, $searchText) {
$this->applySearchToHistory($misQuery, $searchParts, $searchText, 'mis');
});
// ИЛИ проверяем Foxpro историю (только если нет MIS истории)
$subQuery->orWhere(function ($orWhereSubQuery) use ($searchParts, $searchText) {
$orWhereSubQuery->whereNull('mis_history_id') // Нет MIS истории
->whereHas('foxproHistory', function ($foxproQuery) use ($searchParts, $searchText) {
$this->applySearchToHistory($foxproQuery, $searchParts, $searchText, 'foxpro');
});
});
})
// Ищем по архивным номерам
->orWhere('archive_num', 'ILIKE', "{$searchText}%")
->orWhere('mis_num', 'ILIKE', "{$searchText}%")
->orWhere('foxpro_num', 'ILIKE', "{$searchText}%");
});
}
// Фильтрация по дате выписки
if (!empty($dateExtractFrom)) {
$query->where(function ($q) use ($dateExtractFrom) {
$q->whereHas('misHistory', function ($misQuery) use ($dateExtractFrom) {
$misQuery->whereDate('DateExtract', '>=', $dateExtractFrom);
})->orWhereHas('foxproHistory', function ($foxproQuery) use ($dateExtractFrom) {
$foxproQuery->whereDate('menddate', '>=', $dateExtractFrom);
});
});
}
if (!empty($dateExtractTo)) {
$query->where(function ($q) use ($dateExtractTo) {
$q->whereHas('misHistory', function ($misQuery) use ($dateExtractTo) {
$misQuery->whereDate('DateExtract', '<=', $dateExtractTo);
})->orWhereHas('foxproHistory', function ($foxproQuery) use ($dateExtractTo) {
$foxproQuery->whereDate('menddate', '<=', $dateExtractTo);
});
});
}
// Фильтрация по статусу (если передан)
if (!empty($status)) {
$query->where('status_id', $status);
}
// Получаем результаты с пагинацией
$results = $query->paginate($pageSize)
->through(function ($archiveInfo) {
// Приоритет MIS истории
if ($archiveInfo->misHistory) {
$history = $archiveInfo->misHistory;
$history->history_type = 'mis';
} else {
$history = $archiveInfo->foxproHistory;
$history->history_type = 'foxpro';
}
return $history;
});
return $results;
}
private function applySearchToHistory($query, $searchParts, $searchText, $type)
{
if ($type === 'mis') {
$query->where('MedCardNum', 'ILIKE', "{$searchText}%");
// Ищем ФИО в зависимости от количества частей
if (count($searchParts) === 1) {
$query->orWhere('FAMILY', 'ILIKE', "{$searchParts[0]}%")
->orWhere('Name', 'ILIKE', "{$searchParts[0]}%")
->orWhere('OT', 'ILIKE', "{$searchParts[0]}%");
} elseif (count($searchParts) === 2) {
$query->orWhere(function ($subQuery) use ($searchParts) {
$subQuery->where('FAMILY', 'ILIKE', "{$searchParts[0]}%")
->where('Name', 'ILIKE', "{$searchParts[1]}%");
});
} elseif (count($searchParts) === 3) {
$query->orWhere(function ($subQuery) use ($searchParts) {
$subQuery->where('FAMILY', 'ILIKE', "{$searchParts[0]}%")
->where('Name', 'LIKE', "{$searchParts[1]}%")
->where('OT', 'ILIKE', "{$searchParts[2]}%");
});
}
$query->orWhereRaw("CONCAT(\"FAMILY\", ' ', \"Name\", ' ', \"OT\") ILIKE ?", ["{$searchText}%"]);
} else { // foxpro
$query->where('nkarta', 'ILIKE', "{$searchText}%");
if (count($searchParts) === 1) {
$query->orWhere('fam', 'ILIKE', "{$searchParts[0]}%")
->orWhere('im', 'ILIKE', "{$searchParts[0]}%")
->orWhere('ot', 'ILIKE', "{$searchParts[0]}%");
} elseif (count($searchParts) === 2) {
$query->orWhere(function ($subQuery) use ($searchParts) {
$subQuery->where('fam', 'ILIKE', "{$searchParts[0]}%")
->where('im', 'ILIKE', "{$searchParts[1]}%");
});
} elseif (count($searchParts) === 3) {
$query->orWhere(function ($subQuery) use ($searchParts) {
$subQuery->where('fam', 'ILIKE', "{$searchParts[0]}%")
->where('im', 'ILIKE', "{$searchParts[1]}%")
->where('ot', 'ILIKE', "{$searchParts[2]}%");
});
}
$query->orWhereRaw("CONCAT(fam, ' ', im, ' ', ot) ILIKE ?", ["{$searchText}%"]);
}
}
}