Много всего
This commit is contained in:
9
app/Contracts/MedicalHistoryRepositoryInterface.php
Normal file
9
app/Contracts/MedicalHistoryRepositoryInterface.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
interface MedicalHistoryRepositoryInterface
|
||||
{
|
||||
public function get(array $filters);
|
||||
public function search(string $term);
|
||||
}
|
||||
@@ -3,7 +3,10 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\ArchiveHistoryResource;
|
||||
use App\Http\Resources\ArchiveInfoResource;
|
||||
use App\Models\ArchiveHistory;
|
||||
use App\Models\ArchiveInfo;
|
||||
use App\Rules\DateTimeOrStringOrNumber;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
@@ -21,22 +24,20 @@ class ArchiveHistoryController extends Controller
|
||||
return response()->json($archiveHistory);
|
||||
}
|
||||
|
||||
public function move(Request $request)
|
||||
public function moveStore(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'issue_at' => 'nullable|numeric:',
|
||||
'return_at' => 'nullable|numeric:',
|
||||
'issue_at' => ['nullable', new DateTimeOrStringOrNumber],
|
||||
'return_at' => ['nullable', new DateTimeOrStringOrNumber],
|
||||
'org_id' => 'required|numeric',
|
||||
'employee_name' => 'nullable|string',
|
||||
'employee_post' => 'nullable|string',
|
||||
'comment' => 'nullable|string',
|
||||
'has_lost' => 'boolean',
|
||||
'historyable_id' => 'nullable|numeric',
|
||||
'historyable_type' => 'required|string',
|
||||
]);
|
||||
|
||||
// Находим связанную модель ??
|
||||
$historyable = SttMedicalHistory::findOrFail($data['historyable_id']);
|
||||
|
||||
// Преобразуем timestamp в дату, если пришли числа
|
||||
if (isset($data['issue_at']) && is_numeric($data['issue_at'])) {
|
||||
$data['issue_at'] = Carbon::createFromTimestampMs($data['issue_at'])->format('Y-m-d');
|
||||
@@ -48,6 +49,89 @@ class ArchiveHistoryController extends Controller
|
||||
|
||||
$archiveHistory = ArchiveHistory::create($data);
|
||||
|
||||
// Если переданы данные для полиморфной связи
|
||||
if ($request->filled('historyable_id') && $request->filled('historyable_type')) {
|
||||
// Найти связанную модель
|
||||
$historyableClass = $request->input('historyable_type');
|
||||
|
||||
// Проверяем, существует ли класс модели
|
||||
if (class_exists($historyableClass)) {
|
||||
$historyableModel = $historyableClass::find($request->input('historyable_id'));
|
||||
|
||||
if ($historyableModel) {
|
||||
// Связываем модели
|
||||
$archiveHistory->historyable()->associate($historyableModel);
|
||||
$archiveHistory->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(ArchiveHistoryResource::make($archiveHistory));
|
||||
}
|
||||
|
||||
public function moveUpdate($id, Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'issue_at' => ['nullable', new DateTimeOrStringOrNumber],
|
||||
'return_at' => ['nullable', new DateTimeOrStringOrNumber],
|
||||
'org_id' => 'required|numeric',
|
||||
'employee_name' => 'nullable|string',
|
||||
'employee_post' => 'nullable|string',
|
||||
'comment' => 'nullable|string',
|
||||
'has_lost' => 'boolean',
|
||||
'historyable_id' => 'nullable|numeric',
|
||||
'historyable_type' => 'required|string',
|
||||
]);
|
||||
|
||||
// Преобразуем timestamp в дату, если пришли числа
|
||||
if (isset($data['issue_at']) && is_numeric($data['issue_at'])) {
|
||||
$data['issue_at'] = Carbon::createFromTimestampMs($data['issue_at'])->format('Y-m-d');
|
||||
}
|
||||
|
||||
if (isset($data['return_at']) && is_numeric($data['return_at'])) {
|
||||
$data['return_at'] = Carbon::createFromTimestampMs($data['return_at'])->format('Y-m-d');
|
||||
}
|
||||
|
||||
$archiveHistory = ArchiveHistory::find($id);
|
||||
|
||||
$hasUpdated = $archiveHistory->update($data);
|
||||
|
||||
return response()->json(ArchiveHistoryResource::make($archiveHistory));
|
||||
}
|
||||
|
||||
public function infoUpdate($patientId, Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'id' => 'required|numeric',
|
||||
'num' => 'nullable|string',
|
||||
'post_in' => ['nullable', new DateTimeOrStringOrNumber],
|
||||
'historyable_type' => 'required|string',
|
||||
]);
|
||||
|
||||
// Преобразуем timestamp в дату, если пришли числа
|
||||
if (isset($data['post_in']) && is_numeric($data['post_in'])) {
|
||||
$data['post_in'] = Carbon::createFromTimestampMs($data['post_in'])->format('Y-m-d');
|
||||
}
|
||||
|
||||
if ($patientId && $request->filled('historyable_type')) {
|
||||
// Найти связанную модель
|
||||
$historyableClass = $request->input('historyable_type');
|
||||
|
||||
// Проверяем, существует ли класс модели
|
||||
if (class_exists($historyableClass)) {
|
||||
$historyableModel = $historyableClass::find($patientId);
|
||||
|
||||
if ($historyableModel) {
|
||||
// Связываем модели
|
||||
$historyableModel->archiveInfo()->updateOrCreate([
|
||||
'historyable_type' => $historyableClass,
|
||||
'historyable_id' => $patientId,
|
||||
], $data);
|
||||
return response()->json(ArchiveInfoResource::make($historyableModel->archiveInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json()->setStatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,41 +2,119 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\SI\SttMedicalHistoryResource;
|
||||
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 Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
private MedicalHistoryRepository $repository;
|
||||
|
||||
public function __construct(MedicalHistoryRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$pageSize = $request->get('page_size', 15);
|
||||
$pageSize = $request->get('page_size', 50);
|
||||
$searchText = $request->get('search', null);
|
||||
$dateExtractFrom = $request->get('date_extract_from', null);
|
||||
$dateExtractTo = $request->get('date_extract_to', null);
|
||||
$viewType = $request->get('view_type', 'archive');
|
||||
$database = $request->get('database', 'separate'); // si, mis
|
||||
$status = $request->get('status', null);
|
||||
|
||||
$cardsQuery = SttMedicalHistory::query();
|
||||
$data = [];
|
||||
$databaseStats = $this->repository->getDatabaseStats();
|
||||
|
||||
if (!empty($searchText)) {
|
||||
$cardsQuery = $cardsQuery->search($searchText);
|
||||
switch ($database) {
|
||||
case 'si':
|
||||
$paginator = $this->repository->searchInPostgres(
|
||||
$searchText,
|
||||
$dateExtractFrom,
|
||||
$dateExtractTo,
|
||||
$pageSize
|
||||
);
|
||||
$data['si'] = SiSttMedicalHistoryResource::collection($paginator);
|
||||
break;
|
||||
|
||||
case 'mis':
|
||||
$paginator = $this->repository->searchInMssql(
|
||||
$searchText,
|
||||
$dateExtractFrom,
|
||||
$dateExtractTo,
|
||||
$pageSize
|
||||
);
|
||||
$data['mis'] = MisSttMedicalHistoryResource::collection($paginator);
|
||||
break;
|
||||
|
||||
case 'smart':
|
||||
$paginator = $this->repository->smartSearch(
|
||||
$searchText,
|
||||
$dateExtractFrom,
|
||||
$dateExtractTo,
|
||||
$pageSize
|
||||
);
|
||||
$data['smart'] = SiSttMedicalHistoryResource::collection($paginator);
|
||||
break;
|
||||
|
||||
case 'separate':
|
||||
$separateResults = $this->repository->separateSearch(
|
||||
$searchText,
|
||||
$dateExtractFrom,
|
||||
$dateExtractTo,
|
||||
$status,
|
||||
$pageSize
|
||||
);
|
||||
$data = [
|
||||
'si' => SiSttMedicalHistoryResource::collection($separateResults['si']),
|
||||
'mis' => MisSttMedicalHistoryResource::collection($separateResults['mis']),
|
||||
'stats' => $separateResults['stats'],
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($dateExtractFrom)) {
|
||||
$cardsQuery = $cardsQuery->whereDate('dateextract', '>=', $dateExtractFrom);
|
||||
if (!empty($dateExtractTo)) {
|
||||
$cardsQuery = $cardsQuery->whereDate('dateextract', '<=', $dateExtractTo);
|
||||
}
|
||||
}
|
||||
|
||||
$cards = SttMedicalHistoryResource::collection($cardsQuery->paginate($pageSize));
|
||||
$statuses = ArchiveStatus::all()->map(function ($status) {
|
||||
return [
|
||||
'value' => $status->id,
|
||||
'label' => $status->text
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('Home/Index', [
|
||||
'cards' => $cards,
|
||||
'filters' => $request->only([
|
||||
'search', 'date_extract_from', 'date_extract_to', 'page_size', 'page', 'view_type'
|
||||
]),
|
||||
'cards' => $data,
|
||||
'statuses' => $statuses,
|
||||
'databaseStats' => $databaseStats,
|
||||
'filters' => array_merge($request->only([
|
||||
'search', 'date_extract_from', 'date_extract_to',
|
||||
'page_size', 'page', 'view_type', 'database', 'status'
|
||||
]))
|
||||
]);
|
||||
|
||||
// $cardsQuery = SttMedicalHistory::query();
|
||||
//
|
||||
// if (!empty($searchText)) {
|
||||
// $cardsQuery = $cardsQuery->search($searchText);
|
||||
// }
|
||||
//
|
||||
// if (!empty($dateExtractFrom)) {
|
||||
// $cardsQuery = $cardsQuery->whereDate('dateextract', '>=', $dateExtractFrom);
|
||||
// if (!empty($dateExtractTo)) {
|
||||
// $cardsQuery = $cardsQuery->whereDate('dateextract', '<=', $dateExtractTo);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// $cards = SttMedicalHistoryResource::collection($cardsQuery->paginate($pageSize));
|
||||
//
|
||||
// return Inertia::render('Home/Index', [
|
||||
// 'cards' => $cards,
|
||||
// 'filters' => $request->only([
|
||||
// 'search', 'date_extract_from', 'date_extract_to', 'page_size', 'page', 'view_type'
|
||||
// ]),
|
||||
// ]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\ArchiveHistoryResource;
|
||||
use App\Http\Resources\ArchiveInfoResource;
|
||||
use App\Models\SI\SttMedicalHistory;
|
||||
use App\Http\Resources\PatientInfoResource;
|
||||
use App\Models\SI\SttMedicalHistory as SiSttMedicalHistory;
|
||||
use App\Models\Mis\SttMedicalHistory as MisSttMedicalHistory;
|
||||
use App\Repositories\MedicalHistoryRepository;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MedicalHistoryController extends Controller
|
||||
@@ -15,22 +18,27 @@ class MedicalHistoryController extends Controller
|
||||
$patientId = $request->get('patient_id');
|
||||
|
||||
$patientInfo = null;
|
||||
if ($viewType == 'si') {
|
||||
$patient = SttMedicalHistory::where('id', $id)->first();
|
||||
$archiveJournal = ArchiveHistoryResource::collection($patient->archiveHistory);
|
||||
$archiveInfo = $patient->archiveInfo;
|
||||
if ($viewType == 'si') $patient = SiSttMedicalHistory::where('id', $id)->first();
|
||||
else $patient = MisSttMedicalHistory::where('MedicalHistoryID', $id)->first();
|
||||
$archiveJournal = $patient->archiveHistory ? ArchiveHistoryResource::collection($patient->archiveHistory) : null;
|
||||
|
||||
if (!empty($archiveInfo)) {
|
||||
$archiveInfo = ArchiveInfoResource::make($patient->archiveInfo);
|
||||
}
|
||||
|
||||
$patientInfo = [
|
||||
'info' => $patient,
|
||||
'journal' => $archiveJournal,
|
||||
'archiveInfo' => $archiveInfo,
|
||||
];
|
||||
if (!empty($patient->archiveInfo)) {
|
||||
$archiveInfo = ArchiveInfoResource::make($patient->archiveInfo)->toArray(request());
|
||||
} else {
|
||||
$archiveInfo = null;
|
||||
}
|
||||
|
||||
$patientInfo = [
|
||||
'historyable_type' => $viewType == 'si' ? SiSttMedicalHistory::class : MisSttMedicalHistory::class,
|
||||
'info' => [
|
||||
'historyable_type' => $viewType == 'si' ? SiSttMedicalHistory::class : MisSttMedicalHistory::class,
|
||||
...PatientInfoResource::make($patient)->toArray(request()),
|
||||
'can_be_issued' => $patient->canBeIssued()
|
||||
],
|
||||
'journal' => $archiveJournal,
|
||||
'archiveInfo' => $archiveInfo
|
||||
];
|
||||
|
||||
return response()->json($patientInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class ArchiveHistoryResource extends JsonResource
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'issue_at' => Carbon::parse($this->issue_at)->format('d.m.Y'),
|
||||
'return_at' => Carbon::parse($this->return_at)->format('d.m.Y'),
|
||||
'return_at' => $this->return_at ? Carbon::parse($this->return_at)->format('d.m.Y') : null,
|
||||
'comment' => $this->comment,
|
||||
'org_id' => $this->org_id,
|
||||
'org' => $this->org->name,
|
||||
|
||||
@@ -18,7 +18,9 @@ class ArchiveInfoResource extends JsonResource
|
||||
'id' => $this->id,
|
||||
'num' => $this->num,
|
||||
'post_in' => $this->post_in,
|
||||
'status' => $this->status
|
||||
'status' => $this->status,
|
||||
'historyable_id' => $this->historyable_id,
|
||||
'historyable_type' => $this->historyable_type,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
31
app/Http/Resources/Mis/SttMedicalHistoryResource.php
Normal file
31
app/Http/Resources/Mis/SttMedicalHistoryResource.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Mis;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class SttMedicalHistoryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->MedicalHistoryID,
|
||||
'fullname' => $this->getFullNameAttribute(),
|
||||
'daterecipient' => Carbon::parse($this->DateRecipient)->format('d.m.Y'),
|
||||
'dateextract' => Carbon::parse($this->DateExtract)->format('d.m.Y'),
|
||||
'card_num' => $this->archiveInfo->num ?? null,
|
||||
'status' => $this->archiveInfo->status ?? null,
|
||||
'datearhiv' => $this->archiveInfo?->post_at ? Carbon::parse($this->archiveInfo->post_at)->format('d.m.Y') : null,
|
||||
'medcardnum' => $this->MedCardNum,
|
||||
'dr' => Carbon::parse($this->BD)->format('d.m.Y'),
|
||||
'can_be_issue' => $this->canBeIssued()
|
||||
];
|
||||
}
|
||||
}
|
||||
25
app/Http/Resources/PatientInfoResource.php
Normal file
25
app/Http/Resources/PatientInfoResource.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class PatientInfoResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id ?? $this->MedicalHistoryID,
|
||||
'medcardnum' => $this->medcardnum ?? $this->MedCardNum,
|
||||
'family' => $this->family ?? $this->FAMILY,
|
||||
'name' => $this->name ?? $this->Name,
|
||||
'ot' => $this->ot ?? $this->OT,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,10 @@ class SttMedicalHistoryResource extends JsonResource
|
||||
'dateextract' => Carbon::parse($this->dateextract)->format('d.m.Y'),
|
||||
'card_num' => $this->archiveInfo->num ?? null,
|
||||
'status' => $this->archiveInfo->status ?? null,
|
||||
'datearhiv' => Carbon::parse($this->datearhiv)->format('d.m.Y'),
|
||||
'statgod' => $this->statgod,
|
||||
'enp' => $this->enp,
|
||||
'datearhiv' => $this->archiveInfo?->post_at ? Carbon::parse($this->archiveInfo->post_at)->format('d.m.Y') : null,
|
||||
'medcardnum' => $this->medcardnum,
|
||||
'dr' => Carbon::parse($this->dr)->format('d.m.Y'),
|
||||
'can_be_issue' => $this->canBeIssued()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ArchiveHistory extends Model
|
||||
{
|
||||
protected $connection = 'pgsql';
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($archiveHistory) {
|
||||
|
||||
@@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ArchiveInfo extends Model
|
||||
{
|
||||
protected $connection = 'pgsql';
|
||||
protected $table = 'archive_infos';
|
||||
protected $fillable = [
|
||||
'historyable_type',
|
||||
'historyable_id',
|
||||
|
||||
58
app/Models/Mis/SttMedicalHistory.php
Normal file
58
app/Models/Mis/SttMedicalHistory.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Mis;
|
||||
|
||||
use App\Models\ArchiveHistory;
|
||||
use App\Models\ArchiveInfo;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SttMedicalHistory extends Model
|
||||
{
|
||||
protected $primaryKey = 'MedicalHistoryID';
|
||||
protected $table = 'stt_medicalhistory';
|
||||
protected $connection = 'mis';
|
||||
|
||||
public function getFullNameAttribute()
|
||||
{
|
||||
return "$this->FAMILY $this->Name $this->OT";
|
||||
}
|
||||
|
||||
public function archiveHistory()
|
||||
{
|
||||
return $this->morphMany(ArchiveHistory::class, 'historyable');
|
||||
}
|
||||
|
||||
public function archiveInfo()
|
||||
{
|
||||
return $this->morphOne(ArchiveInfo::class, 'historyable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, можно ли выдать эту карту
|
||||
*/
|
||||
public function canBeIssued(): bool
|
||||
{
|
||||
// Проверяем, есть ли открытые выдачи
|
||||
$hasOpenIssue = $this->archiveHistory()
|
||||
->whereNotNull('issue_at')
|
||||
->whereNull('return_at')
|
||||
->where('has_lost', false)
|
||||
->exists();
|
||||
|
||||
return !$hasOpenIssue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает текущую открытую выдачу (если есть)
|
||||
*/
|
||||
public function getCurrentIssue(): ?ArchiveHistory
|
||||
{
|
||||
return $this->archiveHistory()
|
||||
->whereNotNull('issue_at')
|
||||
->whereNull('return_at')
|
||||
->where('has_lost', false)
|
||||
->latest('issue_at')
|
||||
->first();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class SttMedicalHistory extends Model
|
||||
{
|
||||
protected $table = 'si_stt_patients';
|
||||
protected $connection = 'pgsql';
|
||||
|
||||
protected $fillable = [
|
||||
'family', // Фамилия
|
||||
@@ -39,6 +40,34 @@ class SttMedicalHistory extends Model
|
||||
return $this->morphOne(ArchiveInfo::class, 'historyable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, можно ли выдать эту карту
|
||||
*/
|
||||
public function canBeIssued(): bool
|
||||
{
|
||||
// Проверяем, есть ли открытые выдачи
|
||||
$hasOpenIssue = $this->archiveHistory()
|
||||
->whereNotNull('issue_at')
|
||||
->whereNull('return_at')
|
||||
->where('has_lost', false)
|
||||
->exists();
|
||||
|
||||
return !$hasOpenIssue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает текущую открытую выдачу (если есть)
|
||||
*/
|
||||
public function getCurrentIssue(): ?ArchiveHistory
|
||||
{
|
||||
return $this->archiveHistory()
|
||||
->whereNotNull('issue_at')
|
||||
->whereNull('return_at')
|
||||
->where('has_lost', false)
|
||||
->latest('issue_at')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function scopeSearch($query, $searchText)
|
||||
{
|
||||
return $query->where(function($q) use ($searchText) {
|
||||
|
||||
318
app/Repositories/MedicalHistoryRepository.php
Normal file
318
app/Repositories/MedicalHistoryRepository.php
Normal file
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
// app/Repositories/SttMedicalHistoryRepository.php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\SI\SttMedicalHistory as SiMedicalHistory;
|
||||
use App\Models\Mis\SttMedicalHistory as MisMedicalHistory;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MedicalHistoryRepository
|
||||
{
|
||||
protected SiMedicalHistory $siModel;
|
||||
protected MisMedicalHistory $misModel;
|
||||
|
||||
// Поля по умолчанию
|
||||
protected array $defaultFieldsSI = [
|
||||
'id',
|
||||
'family',
|
||||
'name',
|
||||
'ot',
|
||||
'daterecipient',
|
||||
'dateextract',
|
||||
'medcardnum'
|
||||
];
|
||||
|
||||
protected array $defaultFieldsMis = [
|
||||
'MedicalHistoryID',
|
||||
'FAMILY',
|
||||
'Name',
|
||||
'OT',
|
||||
'DateRecipient',
|
||||
'DateExtract',
|
||||
'MedCardNum'
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
SiMedicalHistory $siModel,
|
||||
MisMedicalHistory $misModel
|
||||
) {
|
||||
$this->siModel = $siModel;
|
||||
$this->misModel = $misModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* ПОИСК ТОЛЬКО В POSTGRESQL (основной)
|
||||
*/
|
||||
public function searchInPostgres(
|
||||
?string $searchText,
|
||||
?string $dateFrom,
|
||||
?string $dateTo,
|
||||
?int $status,
|
||||
int $pageSize = 15,
|
||||
string $sortBy = 'dateextract',
|
||||
string $sortDir = 'desc',
|
||||
): LengthAwarePaginator {
|
||||
|
||||
$query = $this->siModel->newQuery();
|
||||
|
||||
$this->applyStatusFilter($query, $status);
|
||||
$this->applyDateFilter($query, 'dateextract', $dateFrom, $dateTo);
|
||||
$this->applySearchConditions($query, $searchText);
|
||||
|
||||
return $query->select($this->defaultFieldsSI)
|
||||
->orderBy($sortBy, $sortDir)
|
||||
->paginate($pageSize)
|
||||
->through(function ($item) {
|
||||
$item->database_source = 'postgresql';
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ПОИСК ТОЛЬКО В MSSQL (исторический)
|
||||
*/
|
||||
public function searchInMssql(
|
||||
?string $searchText,
|
||||
?string $dateFrom,
|
||||
?string $dateTo,
|
||||
?int $status,
|
||||
int $pageSize = 15,
|
||||
string $sortBy = 'DateExtract',
|
||||
string $sortDir = 'desc'
|
||||
): LengthAwarePaginator {
|
||||
|
||||
$query = $this->misModel->newQuery();
|
||||
|
||||
$this->applyStatusFilter($query, $status);
|
||||
$this->applyDateFilter($query, 'DateExtract', $dateFrom, $dateTo);
|
||||
$this->applySearchConditions($query, $searchText, 'mssql');
|
||||
|
||||
return $query->select($this->defaultFieldsMis)
|
||||
->orderBy($sortBy, $sortDir)
|
||||
->paginate($pageSize)
|
||||
->through(function ($item) {
|
||||
$item->database_source = 'mssql';
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* УМНЫЙ ПОИСК (сначала PostgreSQL, потом MSSQL если мало)
|
||||
*/
|
||||
public function smartSearch(
|
||||
?string $searchText,
|
||||
?string $dateFrom,
|
||||
?string $dateTo,
|
||||
int $pageSize = 15
|
||||
): LengthAwarePaginator {
|
||||
|
||||
// 1. Ищем в PostgreSQL
|
||||
$pgPaginator = $this->searchInPostgres($searchText, $dateFrom, $dateTo, $pageSize);
|
||||
|
||||
// 2. Если мало результатов, добавляем из MSSQL
|
||||
if ($pgPaginator->total() < $pageSize && $pgPaginator->total() < 10) {
|
||||
$needed = $pageSize - $pgPaginator->count();
|
||||
|
||||
$mssqlResults = $this->searchInMssql($searchText, $dateFrom, $dateTo, $needed)
|
||||
->getCollection();
|
||||
|
||||
$allResults = $pgPaginator->getCollection()
|
||||
->merge($mssqlResults)
|
||||
->sortByDesc('dateextract')
|
||||
->values();
|
||||
|
||||
return new LengthAwarePaginator(
|
||||
$allResults,
|
||||
$pgPaginator->total() + $mssqlResults->count(),
|
||||
$pageSize,
|
||||
request()->get('page', 1)
|
||||
);
|
||||
}
|
||||
|
||||
return $pgPaginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* РАЗДЕЛЬНЫЙ ПОИСК (отдельные результаты по БД)
|
||||
*/
|
||||
public function separateSearch(
|
||||
?string $searchText,
|
||||
?string $dateFrom,
|
||||
?string $dateTo,
|
||||
?int $status,
|
||||
int $perPage = 15,
|
||||
): array {
|
||||
$pgPaginator = $this->searchInPostgres($searchText, $dateFrom, $dateTo, $status, $perPage);
|
||||
$mssqlPaginator = $this->searchInMssql($searchText, $dateFrom, $dateTo, $status, $perPage);
|
||||
|
||||
return [
|
||||
'si' => $pgPaginator,
|
||||
'mis' => $mssqlPaginator,
|
||||
'stats' => [
|
||||
'si_total' => $pgPaginator->total(),
|
||||
'mis_total' => $mssqlPaginator->total(),
|
||||
'combined_total' => $pgPaginator->total() + $mssqlPaginator->total(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ПРИМЕНЕНИЕ УСЛОВИЙ ПОИСКА
|
||||
*/
|
||||
private function applySearchConditions($query, ?string $searchText, string $dbType = 'postgresql'): void
|
||||
{
|
||||
// Разбиваем поисковую строку на слова
|
||||
$words = preg_split('/\s+/', trim($searchText));
|
||||
$words = array_filter($words);
|
||||
|
||||
if (empty($words)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->where(function($q) use ($words, $dbType) {
|
||||
// Если одно слово - ищем в любом поле
|
||||
if (count($words) === 1) {
|
||||
$word = Str::ucfirst($words[0]);
|
||||
$pattern = $word . '%'; // Префиксный поиск для использования индекса
|
||||
|
||||
if ($dbType === 'postgresql') {
|
||||
$q->where('family', 'LIKE', $pattern)
|
||||
->orWhere('name', 'LIKE', $pattern)
|
||||
->orWhere('ot', 'LIKE', $pattern);
|
||||
} else {
|
||||
$q->where('FAMILY', 'LIKE', $pattern)
|
||||
->orWhere('Name', 'LIKE', $pattern)
|
||||
->orWhere('OT', 'LIKE', $pattern);
|
||||
}
|
||||
}
|
||||
// Если несколько слов - предполагаем Ф+И+О
|
||||
else {
|
||||
// Берем первые 3 слова
|
||||
$family = !empty($words[0]) ? Str::ucfirst($words[0]) : null;
|
||||
$name = !empty($words[1]) ? Str::ucfirst($words[1]) : null;
|
||||
$ot = !empty($words[2]) ? Str::ucfirst($words[2]) : null;
|
||||
|
||||
if ($dbType === 'postgresql') {
|
||||
$q->where('family', 'LIKE', $family . '%');
|
||||
} else {
|
||||
$q->where('FAMILY', 'LIKE', $family . '%');
|
||||
}
|
||||
|
||||
if ($name) {
|
||||
if ($dbType === 'postgresql') {
|
||||
$q->where('name', 'LIKE', $name . '%');
|
||||
} else {
|
||||
$q->where('Name', 'LIKE', $name . '%');
|
||||
}
|
||||
}
|
||||
|
||||
if ($ot) {
|
||||
if ($dbType === 'postgresql') {
|
||||
$q->where('ot', 'LIKE', $ot . '%');
|
||||
} else {
|
||||
$q->where('OT', 'LIKE', $ot . '%');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function applyStatusFilter($query, ?int $value)
|
||||
{
|
||||
if ($query->withExists('archiveInfo') && !empty($value)) {
|
||||
$query->withWhereHas('archiveInfo', function ($q) use ($value) {
|
||||
$q->where('status_id', '=', $value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ФИЛЬТР ПО ДАТЕ
|
||||
*/
|
||||
private function applyDateFilter($query, string $dateField, ?string $dateFrom, ?string $dateTo): void
|
||||
{
|
||||
if (!empty($dateFrom)) {
|
||||
$query->whereDate($dateField, '>=', $dateFrom);
|
||||
}
|
||||
|
||||
if (!empty($dateTo)) {
|
||||
$query->whereDate($dateField, '<=', $dateTo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ПОЛУЧИТЬ СТАТИСТИКУ ПО БАЗАМ
|
||||
*/
|
||||
public function getDatabaseStats(): array
|
||||
{
|
||||
return [
|
||||
'postgresql' => [
|
||||
'total' => $this->siModel->count(),
|
||||
'connection' => config('database.connections.pgsql.database'),
|
||||
'status' => 'primary',
|
||||
],
|
||||
'mssql' => [
|
||||
'total' => $this->misModel->count(),
|
||||
'connection' => config('database.connections.sqlsrv.database'),
|
||||
'status' => 'secondary',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* БЫСТРЫЙ ПОИСК ПО ТИПУ
|
||||
*/
|
||||
public function quickSearch(string $type, string $value): Collection
|
||||
{
|
||||
return match($type) {
|
||||
'medcard' => $this->searchByMedCard($value),
|
||||
'enp' => $this->searchByEnp($value),
|
||||
'fullname' => $this->searchByFullName($value),
|
||||
default => collect(),
|
||||
};
|
||||
}
|
||||
|
||||
private function searchByMedCard(string $medCard): Collection
|
||||
{
|
||||
$pgResults = $this->siModel->where('medcardnum', 'LIKE', "%{$medCard}%")
|
||||
->limit(10)
|
||||
->get()
|
||||
->each(fn($item) => $item->database_source = 'postgresql');
|
||||
|
||||
$mssqlResults = $this->misModel->where('medcardnum', 'LIKE', "%{$medCard}%")
|
||||
->limit(10)
|
||||
->get()
|
||||
->each(fn($item) => $item->database_source = 'mssql');
|
||||
|
||||
return $pgResults->concat($mssqlResults)
|
||||
->sortBy('medcardnum')
|
||||
->values();
|
||||
}
|
||||
|
||||
private function searchByFullName(string $name): Collection
|
||||
{
|
||||
$pattern = "%{$name}%";
|
||||
|
||||
$pgResults = $this->siModel->where('family', 'ILIKE', $pattern)
|
||||
->orWhere('name', 'ILIKE', $pattern)
|
||||
->orWhere('ot', 'ILIKE', $pattern)
|
||||
->limit(10)
|
||||
->get()
|
||||
->each(fn($item) => $item->database_source = 'postgresql');
|
||||
|
||||
$mssqlResults = $this->misModel->where('family', 'LIKE', $pattern)
|
||||
->orWhere('name', 'LIKE', $pattern)
|
||||
->orWhere('ot', 'LIKE', $pattern)
|
||||
->limit(10)
|
||||
->get()
|
||||
->each(fn($item) => $item->database_source = 'mssql');
|
||||
|
||||
return $pgResults->concat($mssqlResults)
|
||||
->sortBy('family')
|
||||
->values();
|
||||
}
|
||||
}
|
||||
42
app/Rules/DateTimeOrStringOrNumber.php
Normal file
42
app/Rules/DateTimeOrStringOrNumber.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class DateTimeOrStringOrNumber implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
// Если null - пропускаем
|
||||
if (is_null($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Если число
|
||||
if (is_numeric($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Если строка
|
||||
if (is_string($value)) {
|
||||
// Проверяем, можно ли преобразовать в дату
|
||||
try {
|
||||
Carbon::parse($value);
|
||||
return;
|
||||
} catch (\Exception $e) {
|
||||
// Если не дата, но это строка - все равно ок
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$fail('Ошибка при проверки даты');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user