diff --git a/app/Http/Controllers/ArchiveHistoryController.php b/app/Http/Controllers/ArchiveHistoryController.php
index 99b173a..543e2b9 100644
--- a/app/Http/Controllers/ArchiveHistoryController.php
+++ b/app/Http/Controllers/ArchiveHistoryController.php
@@ -6,6 +6,7 @@ use App\Http\Resources\ArchiveHistoryResource;
use App\Http\Resources\ArchiveInfoResource;
use App\Models\ArchiveHistory;
use App\Models\ArchiveInfo;
+use App\Models\SI\SttMedicalHistory;
use App\Rules\DateTimeOrStringOrNumber;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
@@ -21,7 +22,10 @@ class ArchiveHistoryController extends Controller
{
$archiveHistory = ArchiveHistory::find($id);
- return response()->json($archiveHistory);
+ return response()->json([
+ ...$archiveHistory->toArray(),
+ 'type' => $archiveHistory->historyType()
+ ]);
}
public function moveStore(Request $request)
@@ -34,36 +38,46 @@ class ArchiveHistoryController extends Controller
'employee_post' => 'nullable|string',
'comment' => 'nullable|string',
'has_lost' => 'boolean',
- 'historyable_id' => 'nullable|numeric',
- 'historyable_type' => 'required|string',
+ 'archive_info_id' => 'required|numeric',
+ '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');
+ $data['issue_at'] = Carbon::createFromTimestampMs($data['issue_at'])
+ ->setTimezone(config('app.timezone'))
+ ->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');
+ $data['return_at'] = Carbon::createFromTimestampMs($data['return_at'])
+ ->setTimezone(config('app.timezone'))
+ ->format('Y-m-d');
}
- $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();
- }
- }
+ $archiveInfo = ArchiveInfo::whereId($data['archive_info_id'])->first();
+ if ($data['type'] === 'mis') {
+ $archiveHistory = $archiveInfo->misHistory->archiveHistory()->create([
+ 'issue_at' => $data['issue_at'],
+ 'return_at' => $data['return_at'],
+ 'org_id' => $data['org_id'],
+ 'employee_name' => $data['employee_name'],
+ 'employee_post' => $data['employee_post'],
+ 'comment' => $data['comment'],
+ 'has_lost' => $data['has_lost'],
+ 'mis_history_id' => $archiveInfo->mis_history_id
+ ]);
+ } else {
+ $archiveHistory = $archiveInfo->foxproHistory->archiveHistory()->create([
+ 'issue_at' => $data['issue_at'],
+ 'return_at' => $data['return_at'],
+ 'org_id' => $data['org_id'],
+ 'employee_name' => $data['employee_name'],
+ 'employee_post' => $data['employee_post'],
+ 'comment' => $data['comment'],
+ 'has_lost' => $data['has_lost'],
+ 'foxpro_history_id' => $archiveInfo->foxpro_history_id,
+ ]);
}
return response()->json(ArchiveHistoryResource::make($archiveHistory));
@@ -79,17 +93,20 @@ class ArchiveHistoryController extends Controller
'employee_post' => 'nullable|string',
'comment' => 'nullable|string',
'has_lost' => 'boolean',
- 'historyable_id' => 'nullable|numeric',
- 'historyable_type' => 'required|string',
+ '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');
+ $data['issue_at'] = Carbon::createFromTimestampMs($data['issue_at'])
+ ->setTimezone(config('app.timezone'))
+ ->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');
+ $data['return_at'] = Carbon::createFromTimestampMs($data['return_at'])
+ ->setTimezone(config('app.timezone'))
+ ->format('Y-m-d');
}
$archiveHistory = ArchiveHistory::find($id);
@@ -105,7 +122,8 @@ class ArchiveHistoryController extends Controller
'id' => 'required|numeric',
'num' => 'nullable|string',
'post_in' => ['nullable', new DateTimeOrStringOrNumber],
- 'historyable_type' => 'required|string',
+ 'status' => 'nullable',
+ 'type' => 'nullable'
]);
// Преобразуем timestamp в дату, если пришли числа
@@ -113,25 +131,16 @@ class ArchiveHistoryController extends Controller
$data['post_in'] = Carbon::createFromTimestampMs($data['post_in'])->format('Y-m-d');
}
- if ($patientId && $request->filled('historyable_type')) {
- // Найти связанную модель
- $historyableClass = $request->input('historyable_type');
+ $archiveInfo = ArchiveInfo::whereId($patientId)->first();
- // Проверяем, существует ли класс модели
- if (class_exists($historyableClass)) {
- $historyableModel = $historyableClass::find($patientId);
+ $archiveInfo->updateOrCreate(
+ ['id' => $patientId],
+ [
+ 'archive_num' => $data['num'],
+ 'post_in' => $data['post_in'],
+ ]
+ );
- if ($historyableModel) {
- // Связываем модели
- $historyableModel->archiveInfo()->updateOrCreate([
- 'historyable_type' => $historyableClass,
- 'historyable_id' => $patientId,
- ], $data);
- return response()->json(ArchiveInfoResource::make($historyableModel->archiveInfo));
- }
- }
- }
-
- return response()->json()->setStatusCode(500);
+ return response()->json()->setStatusCode(200);
}
}
diff --git a/app/Http/Controllers/IndexController.php b/app/Http/Controllers/IndexController.php
index bdcd486..6346769 100644
--- a/app/Http/Controllers/IndexController.php
+++ b/app/Http/Controllers/IndexController.php
@@ -25,58 +25,15 @@ class IndexController extends Controller
$searchText = $request->get('search', null);
$dateExtractFrom = $request->get('date_extract_from', null);
$dateExtractTo = $request->get('date_extract_to', null);
- $database = $request->get('database', 'separate'); // si, mis
$status = $request->get('status', null);
- $data = [];
- $databaseStats = $this->repository->getDatabaseStats();
-
- 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;
- }
+ $data = $this->repository->unifiedSearch(
+ $searchText,
+ $dateExtractFrom,
+ $dateExtractTo,
+ $status,
+ $pageSize
+ );
$statuses = ArchiveStatus::all()->map(function ($status) {
return [
@@ -91,35 +48,13 @@ class IndexController extends Controller
]);
return Inertia::render('Home/Index', [
- 'cards' => $data,
+ 'cards' => MisSttMedicalHistoryResource::collection($data),
'statuses' => $statuses,
- 'databaseStats' => $databaseStats,
'filters' => array_merge($request->only([
'search', 'date_extract_from', 'date_extract_to',
- 'page_size', 'page', 'view_type', 'database', 'status'
+ 'page_size', 'page', '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'
-// ]),
-// ]);
}
}
diff --git a/app/Http/Controllers/MedicalHistoryController.php b/app/Http/Controllers/MedicalHistoryController.php
index a644cfc..ae4f6f7 100644
--- a/app/Http/Controllers/MedicalHistoryController.php
+++ b/app/Http/Controllers/MedicalHistoryController.php
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Http\Resources\ArchiveHistoryResource;
use App\Http\Resources\ArchiveInfoResource;
use App\Http\Resources\PatientInfoResource;
+use App\Models\ArchiveInfo;
use App\Models\SI\SttMedicalHistory as SiSttMedicalHistory;
use App\Models\Mis\SttMedicalHistory as MisSttMedicalHistory;
use App\Repositories\MedicalHistoryRepository;
@@ -14,24 +15,25 @@ class MedicalHistoryController extends Controller
{
public function patient($id, Request $request)
{
- $viewType = $request->get('view_type', 'si');
+ $viewType = $request->get('view_type', 'mis');
$patientId = $request->get('patient_id');
- $patientInfo = null;
- if ($viewType == 'si') $patient = SiSttMedicalHistory::where('id', $id)->first();
- else $patient = MisSttMedicalHistory::where('MedicalHistoryID', $id)->first();
- $archiveJournal = $patient->archiveHistory ? ArchiveHistoryResource::collection($patient->archiveHistory) : null;
+ $archiveInfo = ArchiveInfo::whereId($id)->first()->load('status');
+ if ($viewType == 'foxpro') $patient = $archiveInfo->foxproHistory;
+ else $patient = $archiveInfo->misHistory;
- if (!empty($patient->archiveInfo)) {
- $archiveInfo = ArchiveInfoResource::make($patient->archiveInfo)->toArray(request());
+ $archiveJournal = $patient->archiveHistory ? ArchiveHistoryResource::collection($patient->archiveHistory) : null;
+// dd($archiveInfo);
+ if (!empty($archiveInfo)) {
+ $archiveInfo = ArchiveInfoResource::make($archiveInfo)->toArray(request());
} else {
$archiveInfo = null;
}
$patientInfo = [
- 'historyable_type' => $viewType == 'si' ? SiSttMedicalHistory::class : MisSttMedicalHistory::class,
+ 'historyable_type' => $viewType == 'foxpro' ? SiSttMedicalHistory::class : MisSttMedicalHistory::class,
'info' => [
- 'historyable_type' => $viewType == 'si' ? SiSttMedicalHistory::class : MisSttMedicalHistory::class,
+ 'historyable_type' => $viewType == 'foxpro' ? SiSttMedicalHistory::class : MisSttMedicalHistory::class,
...PatientInfoResource::make($patient)->toArray(request()),
'can_be_issued' => $patient->canBeIssued()
],
diff --git a/app/Http/Resources/ArchiveHistoryResource.php b/app/Http/Resources/ArchiveHistoryResource.php
index 05fa35c..146d5dd 100644
--- a/app/Http/Resources/ArchiveHistoryResource.php
+++ b/app/Http/Resources/ArchiveHistoryResource.php
@@ -21,7 +21,7 @@ class ArchiveHistoryResource extends JsonResource
'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,
+ 'org' => $this->org?->name,
'employee_name' => $this->employee_name,
'employee_post' => $this->employee_post,
'has_lost' => $this->has_lost,
diff --git a/app/Http/Resources/ArchiveInfoResource.php b/app/Http/Resources/ArchiveInfoResource.php
index 10b6731..7d189d9 100644
--- a/app/Http/Resources/ArchiveInfoResource.php
+++ b/app/Http/Resources/ArchiveInfoResource.php
@@ -16,11 +16,12 @@ class ArchiveInfoResource extends JsonResource
{
return [
'id' => $this->id,
- 'num' => $this->num,
+ 'num' => $this->archive_num,
'post_in' => $this->post_in,
'status' => $this->status,
- 'historyable_id' => $this->historyable_id,
- 'historyable_type' => $this->historyable_type,
+ 'foxpro_num' => $this->foxpro_num,
+ 'mis_num' => $this->mis_num,
+ 'type' => $this->historyType()
];
}
}
diff --git a/app/Http/Resources/Mis/SttMedicalHistoryResource.php b/app/Http/Resources/Mis/SttMedicalHistoryResource.php
index 3195cba..c5b74cf 100644
--- a/app/Http/Resources/Mis/SttMedicalHistoryResource.php
+++ b/app/Http/Resources/Mis/SttMedicalHistoryResource.php
@@ -2,9 +2,9 @@
namespace App\Http\Resources\Mis;
+use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
-use Illuminate\Support\Carbon;
class SttMedicalHistoryResource extends JsonResource
{
@@ -15,17 +15,114 @@ class SttMedicalHistoryResource extends JsonResource
*/
public function toArray(Request $request): array
{
+ // Определяем источник данных
+ $isFromArchive = $this->resource['in_archive'] ?? false;
+ $isTemporary = $this->resource['is_temporary'] ?? false;
+ $historyType = $this->resource['history_type'] ?? 'mis';
+
+ // Формируем ФИО
+ $family = $this->resource['family'] ?? '';
+ $name = $this->resource['name'] ?? '';
+ $ot = $this->resource['ot'] ?? '';
+
+ // Для временных записей (не в архиве) используем данные из MIS
+ if ($isTemporary) {
+ // Данные из stt_medicalhistory (не в архиве)
+ $fullName = trim("{$family} {$name} {$ot}");
+ $birthDate = $this->resource['birth_date'] ?? null;
+ $dateExtract = $this->resource['date_extract'] ?? null;
+ $dateRecipient = null; // Для MIS записей не в архиве может не быть
+ $cardNumber = $this->resource['card_number'] ?? null;
+ $archiveNum = null;
+ $postIn = null;
+ $status = $this->resource['status_text'] ?? 'Не в архиве';
+ } else {
+ // Данные из archive_infos (в архиве)
+ $fullName = trim("{$family} {$name} {$ot}");
+ $birthDate = $this->resource['birth_date'] ?? null;
+
+ // Для архивных записей date_extract может быть из MIS или FoxPro
+ $dateExtract = $this->resource['date_extract'] ?? null;
+
+ // Для MIS записей в архиве
+ if ($historyType === 'mis') {
+ $dateRecipient = $this->resource['date_recipient'] ?? null;
+ } else {
+ // Для FoxPro записей в архиве
+ $dateRecipient = $this->resource['mpostdate'] ?? null;
+ }
+
+ $cardNumber = $this->resource['card_number'] ?? null;
+ $archiveNum = $this->resource['archive_num'] ?? null;
+ $postIn = $this->resource['post_in'] ?? null;
+ $status = $this->resource['status_text'] ?? 'Неизвестно';
+ }
+
+ // Форматирование дат
+ $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;
+
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->whenLoaded('archiveInfo') ? Carbon::parse($this->archiveInfo->post_in)->format('d.m.Y') : null,
- 'medcardnum' => $this->MedCardNum,
- 'dr' => Carbon::parse($this->BD)->format('d.m.Y'),
- 'can_be_issue' => $this->canBeIssued()
+ 'id' => $this->resource['id'],
+ 'history_type' => $historyType,
+ 'in_archive' => $isFromArchive,
+ 'is_temporary' => $isTemporary,
+ 'source' => $this->resource['source'] ?? 'archive',
+
+ // Основные данные
+ 'fullname' => $fullName,
+ 'family' => $family,
+ 'name' => $name,
+ 'ot' => $ot,
+ 'dr' => $formattedBirthDate,
+ 'daterecipient' => $formattedDateRecipient,
+ 'dateextract' => $formattedDateExtract,
+
+ // Номера карт
+ 'medcardnum' => $cardNumber, // MIS номер или FoxPro номер
+ 'mis_card_number' => $this->resource['mis_card_number'] ?? null, // Оригинальный MIS номер
+ 'foxpro_card_number' => $this->resource['foxpro_card_number'] ?? null, // Оригинальный 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,
+
+ // Дополнительные идентификаторы
+ 'mis_history_id' => $this->resource['mis_history_id'] ?? null,
+ 'foxpro_history_id' => $this->resource['foxpro_history_id'] ?? null,
+
+ // Дополнительные данные
+ 'snils' => $this->resource['snils'] ?? null,
+ 'enp' => $this->resource['enp'] ?? null,
+// 'created_at' => $this->resource->created_at ? Carbon::parse($this->resource->created_at)->format('d.m.Y H:i') : null,
+// 'updated_at' => $this->resource->updated_at ? Carbon::parse($this->resource->updated_at)->format('d.m.Y H:i') : null,
+
+ // Стили для фронта
+ 'row_class' => $this->resource['row_class'] ?? '',
+ 'status_color' => $this->getStatusColor($this->resource['status_id'] ?? 0, $isFromArchive),
];
}
+
+ /**
+ * Получение цвета статуса для фронта
+ */
+ private function getStatusColor(int $statusId, bool $inArchive): string
+ {
+ if (!$inArchive) {
+ return 'warning'; // Желтый для не в архиве
+ }
+
+ return match($statusId) {
+ 1 => 'success', // Зеленый для в архиве
+ 2 => 'info', // Синий для выдано
+ 3, 4 => 'danger', // Красный для утрачено/списано
+ default => 'secondary',
+ };
+ }
}
diff --git a/app/Http/Resources/PatientInfoResource.php b/app/Http/Resources/PatientInfoResource.php
index 7abe469..3d368f5 100644
--- a/app/Http/Resources/PatientInfoResource.php
+++ b/app/Http/Resources/PatientInfoResource.php
@@ -15,10 +15,10 @@ class PatientInfoResource extends JsonResource
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,
+ 'id' => $this->keykarta ?? $this->MedicalHistoryID,
+ 'medcardnum' => $this->nkarta ?? $this->MedCardNum,
+ 'family' => $this->fam ?? $this->FAMILY,
+ 'name' => $this->im ?? $this->Name,
'ot' => $this->ot ?? $this->OT,
];
}
diff --git a/app/Models/ArchiveHistory.php b/app/Models/ArchiveHistory.php
index 68ebc2c..5c77800 100644
--- a/app/Models/ArchiveHistory.php
+++ b/app/Models/ArchiveHistory.php
@@ -2,6 +2,8 @@
namespace App\Models;
+use App\Models\Mis\SttMedicalHistory as MisMedicalHistory;
+use App\Models\SI\SttMedicalHistory as SiMedicalHistory;
use Illuminate\Database\Eloquent\Model;
class ArchiveHistory extends Model
@@ -54,8 +56,9 @@ class ArchiveHistory extends Model
}
protected $fillable = [
- 'historyable_type',
- 'historyable_id',
+ 'keyarhiv',
+ 'foxpro_history_id',
+ 'mis_history_id',
'issue_at',
'return_at',
'comment',
@@ -65,9 +68,19 @@ class ArchiveHistory extends Model
'has_lost',
];
- public function historyable()
+ public function foxproHistory()
{
- return $this->morphTo();
+ return $this->belongsTo(SiMedicalHistory::class, 'keykarta', 'foxpro_history_id');
+ }
+
+ public function misHistory()
+ {
+ return $this->belongsTo(MisMedicalHistory::class, 'MedicalHistoryID', 'mis_history_id');
+ }
+
+ public function historyType()
+ {
+ return $this->mis_history_id !== null ? 'mis' : 'foxpro';
}
public function org(): \Illuminate\Database\Eloquent\Relations\BelongsTo
diff --git a/app/Models/ArchiveInfo.php b/app/Models/ArchiveInfo.php
index b264279..24e8ab7 100644
--- a/app/Models/ArchiveInfo.php
+++ b/app/Models/ArchiveInfo.php
@@ -2,6 +2,8 @@
namespace App\Models;
+use App\Models\Mis\SttMedicalHistory as MisMedicalHistory;
+use App\Models\SI\SttMedicalHistory as SiMedicalHistory;
use Illuminate\Database\Eloquent\Model;
class ArchiveInfo extends Model
@@ -16,17 +18,30 @@ class ArchiveInfo extends Model
protected $connection = 'pgsql';
protected $table = 'archive_infos';
+
protected $fillable = [
- 'historyable_type',
- 'historyable_id',
- 'num',
+ 'foxpro_history_id',
+ 'mis_history_id',
+ 'foxpro_num',
+ 'mis_num',
+ 'archive_num',
'post_in',
'status_id'
];
- public function historyable()
+ public function foxproHistory()
{
- return $this->morphTo();
+ return $this->belongsTo(SiMedicalHistory::class, 'foxpro_history_id', 'keykarta');
+ }
+
+ public function misHistory()
+ {
+ return $this->belongsTo(MisMedicalHistory::class, 'mis_history_id', 'MedicalHistoryID');
+ }
+
+ public function historyType()
+ {
+ return $this->mis_history_id !== null ? 'mis' : 'foxpro';
}
public function status()
diff --git a/app/Models/Mis/SttMedicalHistory.php b/app/Models/Mis/SttMedicalHistory.php
index f91dd30..e7c9253 100644
--- a/app/Models/Mis/SttMedicalHistory.php
+++ b/app/Models/Mis/SttMedicalHistory.php
@@ -11,6 +11,11 @@ class SttMedicalHistory extends Model
{
protected $primaryKey = 'MedicalHistoryID';
protected $table = 'stt_medicalhistory';
+ protected $keyType = 'string';
+
+ protected $casts = [
+ 'MedicalHistoryID' => 'string',
+ ];
public function getFullNameAttribute()
{
@@ -19,12 +24,12 @@ class SttMedicalHistory extends Model
public function archiveHistory()
{
- return $this->morphMany(ArchiveHistory::class, 'historyable');
+ return $this->hasMany(ArchiveHistory::class, 'mis_history_id', 'MedicalHistoryID');
}
public function archiveInfo()
{
- return $this->morphOne(ArchiveInfo::class, 'historyable');
+ return $this->hasOne(ArchiveInfo::class, 'mis_history_id', 'MedicalHistoryID');
}
/**
@@ -41,7 +46,6 @@ class SttMedicalHistory extends Model
$hasNotBadStatus = $this->archiveInfo()
->whereNotNull('status_id')
- ->whereNot('status_id', 1)
->whereNot('status_id', 3)
->whereNot('status_id', 4)
->exists();
diff --git a/app/Models/SI/SttMedicalHistory.php b/app/Models/SI/SttMedicalHistory.php
index 43c2248..e759763 100644
--- a/app/Models/SI/SttMedicalHistory.php
+++ b/app/Models/SI/SttMedicalHistory.php
@@ -8,36 +8,43 @@ use Illuminate\Database\Eloquent\Model;
class SttMedicalHistory extends Model
{
- protected $table = 'si_stt_patients';
+ protected $table = 'foxpro_original_patient';
+ protected $primaryKey = 'keykarta';
protected $connection = 'pgsql';
+ protected $keyType = 'string';
+ public $incrementing = false;
+
+ protected $casts = [
+ 'keykarta' => 'string',
+ ];
protected $fillable = [
- 'family', // Фамилия
- 'name', // Имя
+ 'fam', // Фамилия
+ 'im', // Имя
'ot', // Отчество
- 'daterecipient', // Д. пост. (mpostdate)
- 'dateextract', // Д. вып. (menddate)
+ 'mpostdate', // Д. пост. (mpostdate)
+ 'menddate', // Д. вып. (menddate)
'narhiv', // № в архиве
'datearhiv', // Д. архив
- 'statgod', // Год нахождения в стационаре
+ 'snils', // Год нахождения в стационаре
'enp', // ЕНП
- 'medcardnum', // № карты
+ 'nkarta', // № карты
'dr', // ДР
];
public function getFullNameAttribute()
{
- return "$this->family $this->name $this->ot";
+ return "$this->fam $this->im $this->ot";
}
public function archiveHistory()
{
- return $this->morphMany(ArchiveHistory::class, 'historyable');
+ return $this->hasMany(ArchiveHistory::class, 'foxpro_history_id', 'keykarta');
}
public function archiveInfo()
{
- return $this->morphOne(ArchiveInfo::class, 'historyable');
+ return $this->hasOne(ArchiveInfo::class, 'foxpro_history_id', 'keykarta');
}
/**
diff --git a/app/Repositories/MedicalHistoryRepository.php b/app/Repositories/MedicalHistoryRepository.php
index fb0377a..8ae76a4 100644
--- a/app/Repositories/MedicalHistoryRepository.php
+++ b/app/Repositories/MedicalHistoryRepository.php
@@ -1,327 +1,941 @@
siModel = $siModel;
- $this->misModel = $misModel;
- }
-
/**
- * ПОИСК ТОЛЬКО В POSTGRESQL (основной)
+ * Основной метод поиска через RAW SQL
*/
- public function searchInPostgres(
+ public function unifiedSearch(
?string $searchText,
?string $dateFrom,
?string $dateTo,
?int $status,
- int $pageSize = 15,
- string $sortBy = 'dateextract',
- string $sortDir = 'desc',
- ): LengthAwarePaginator {
+ int $pageSize = 50
+ ): LengthAwarePaginator
+ {
+ $page = request()->get('page', 1);
+ $offset = ($page - 1) * $pageSize;
- $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)
- ->with(['archiveInfo'])
- ->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)
- ->with(['archiveInfo'])
- ->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();
+ // 1. Получаем только ID и минимальные данные для пагинации
+ $idQuery = $this->buildPaginatedIdQuery($searchText, $dateFrom, $dateTo, $status, $pageSize, $offset);
+ $idResults = DB::select($idQuery['sql'], $idQuery['params']);
+ if (empty($idResults)) {
return new LengthAwarePaginator(
- $allResults,
- $pgPaginator->total() + $mssqlResults->count(),
+ collect(),
+ 0,
$pageSize,
- request()->get('page', 1)
+ $page,
+ ['path' => request()->url(), 'query' => request()->query()]
);
}
- return $pgPaginator;
+ // 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 с пагинацией
*/
- public function separateSearch(
+ private function buildPaginatedIdQuery(
?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);
+ 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 [
- 'si' => $pgPaginator,
- 'mis' => $mssqlPaginator,
- 'stats' => [
- 'si_total' => $pgPaginator->total(),
- 'mis_total' => $mssqlPaginator->total(),
- 'combined_total' => $pgPaginator->total() + $mssqlPaginator->total(),
- ]
+ 'conditions' => $conditions,
+ 'params' => $params
];
}
/**
- * ПРИМЕНЕНИЕ УСЛОВИЙ ПОИСКА
+ * Условия для MIS записей не в архиве
*/
- private function applySearchConditions($query, ?string $searchText, string $dbType = 'postgresql'): void
+ 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;
+ return $conditions;
}
- $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(Builder $query, ?int $value)
- {
- if ($value === 0) {
- $query->doesntHave('archiveInfo');
- } else {
- if ($query->withExists('archiveInfo') && !empty($value)) {
- $query->withWhereHas('archiveInfo', function ($q) use ($value) {
- $q->where('status_id', '=', $value);
- });
- }
+ // Если одно слово - ищем по всем полям
+ 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 applyDateFilter($query, string $dateField, ?string $dateFrom, ?string $dateTo): void
+ private function getArchiveDataByIds(array $ids): array
{
- if (!empty($dateFrom)) {
- $query->whereDate($dateField, '>=', $dateFrom);
+ if (empty($ids)) {
+ return [];
}
- if (!empty($dateTo)) {
- $query->whereDate($dateField, '<=', $dateTo);
- }
+ $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
*/
- public function getDatabaseStats(): array
+ 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 [
- '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',
- ]
+ '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),
];
}
- /**
- * БЫСТРЫЙ ПОИСК ПО ТИПУ
- */
- public function quickSearch(string $type, string $value): Collection
+ private function getStatusText(int $statusId, bool $inArchive): string
{
- return match($type) {
- 'medcard' => $this->searchByMedCard($value),
- 'enp' => $this->searchByEnp($value),
- 'fullname' => $this->searchByFullName($value),
- default => collect(),
+ if (!$inArchive) {
+ return 'Не в архиве';
+ }
+
+ return match($statusId) {
+ 1 => 'В архиве',
+ 2 => 'Выдано',
+ 3 => 'Утрачено',
+ 4 => 'Списано',
+ default => 'Неизвестно',
};
}
- private function searchByMedCard(string $medCard): Collection
+ private function canBeIssued(int $statusId, bool $inArchive): bool
{
- $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();
+ return $inArchive && $statusId === 1;
}
- private function searchByFullName(string $name): Collection
+ private function getRowClass(int $statusId, bool $inArchive): string
{
- $pattern = "%{$name}%";
+ if (!$inArchive) {
+ return 'table-warning';
+ }
- $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');
+ return match($statusId) {
+ 1 => 'table-success',
+ 2 => 'table-info',
+ 3, 4 => 'table-danger',
+ default => '',
+ };
+ }
- $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');
+ /**
+ * Быстрый поиск для автокомплита
+ */
+ public function quickSearch(string $searchText, int $limit = 20): array
+ {
+ if (is_numeric($searchText)) {
+ return $this->quickSearchByCardNumber($searchText, $limit);
+ }
- return $pgResults->concat($mssqlResults)
- ->sortBy('family')
- ->values();
+ 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;
}
}
diff --git a/app/Services/ArchiveSearchService.php b/app/Services/ArchiveSearchService.php
new file mode 100644
index 0000000..cbfa5d6
--- /dev/null
+++ b/app/Services/ArchiveSearchService.php
@@ -0,0 +1,146 @@
+searchInArchiveInfos($searchText);
+ if ($archiveInfo) {
+ return $archiveInfo;
+ }
+
+ // 2. Если нет в archive_infos, ищем в MIS
+ $misHistory = $this->searchInMis($searchText);
+ if ($misHistory) {
+ // Создаем запись в archive_infos для MIS карты
+ return $this->createArchiveInfoForMis($misHistory);
+ }
+
+ // 3. Если нет в MIS, ищем в FoxPro
+ $foxproHistory = $this->searchInFoxPro($searchText);
+ if ($foxproHistory) {
+ // Создаем запись в archive_infos для FoxPro карты
+ return $this->createArchiveInfoForFoxPro($foxproHistory);
+ }
+
+ return null;
+ }
+
+ /**
+ * Поиск по ФИО или другим критериям
+ */
+ public function searchByName(string $searchText): array
+ {
+ // Поиск в MIS
+ $misResults = MisMedicalHistory::query()
+ ->where(function($query) use ($searchText) {
+ $query->whereRaw("CONCAT(\"FAMILY\", ' ', \"Name\", ' ', COALESCE(\"OT\", '')) ILIKE ?", ["$searchText%"])
+ ->orWhere('FAMILY', 'ILIKE', "$searchText%")
+ ->orWhere('Name', 'ILIKE', "$searchText%")
+ ->orWhere('OT', 'ILIKE', "$searchText%");
+ })
+ ->get();
+
+ return $misResults;
+ }
+
+ /**
+ * Поиск в archive_infos
+ */
+ private function searchInArchiveInfos(string $cardNumber): ?ArchiveInfo
+ {
+ return ArchiveInfo::query()
+ ->where('mis_num', $cardNumber)
+ ->orWhere('foxpro_num', $cardNumber)
+ ->with(['misHistory', 'foxproHistory'])
+ ->first();
+ }
+
+ /**
+ * Поиск в MIS
+ */
+ private function searchInMis(string $cardNumber): ?MisMedicalHistory
+ {
+ return MisMedicalHistory::where('MedCardNum', $cardNumber)->first();
+ }
+
+ /**
+ * Поиск в FoxPro
+ */
+ private function searchInFoxPro(string $cardNumber): ?SiMedicalHistory
+ {
+ return SiMedicalHistory::where('nkarta', $cardNumber)->first();
+ }
+
+ /**
+ * Создание записи в archive_infos для MIS карты
+ */
+ private function createArchiveInfoForMis(MisMedicalHistory $misHistory): ArchiveInfo
+ {
+ // Проверяем, нет ли уже записи
+ $existingArchiveInfo = ArchiveInfo::where('mis_history_id', $misHistory->MedicalHistoryID)->first();
+ if ($existingArchiveInfo) {
+ return $existingArchiveInfo;
+ }
+
+ // Ищем связанную запись в FoxPro
+ $foxproHistory = SiMedicalHistory::where('snils', $misHistory->SS)
+ ->where('menddate', $misHistory->DateExtract)
+ ->where('dr', '!=', $misHistory->BD)
+ ->first();
+
+ // Создаем запись
+ return ArchiveInfo::create([
+ 'mis_history_id' => $misHistory->MedicalHistoryID,
+ 'mis_num' => $misHistory->MedCardNum,
+ 'foxpro_history_id' => $foxproHistory?->keykarta,
+ 'foxpro_num' => $foxproHistory?->nkarta,
+ 'archive_num' => $foxproHistory?->narhiv,
+ 'post_in' => $foxproHistory?->datearhiv,
+ 'status_id' => 2, // Статус по умолчанию
+ ]);
+ }
+
+ /**
+ * Создание записи в archive_infos для FoxPro карты
+ */
+ private function createArchiveInfoForFoxPro(SiMedicalHistory $foxproHistory): ArchiveInfo
+ {
+ // Проверяем, нет ли уже записи
+ $existingArchiveInfo = ArchiveInfo::where('foxpro_history_id', $foxproHistory->keykarta)->first();
+ if ($existingArchiveInfo) {
+ return $existingArchiveInfo;
+ }
+
+ // Ищем связанную запись в MIS
+ $misHistory = MisMedicalHistory::where('SS', $foxproHistory->snils)
+ ->where('DateExtract', $foxproHistory->menddate)
+ ->where('BD', '!=', $foxproHistory->dr)
+ ->first();
+
+ // Создаем запись
+ return ArchiveInfo::create([
+ 'foxpro_history_id' => $foxproHistory->keykarta,
+ 'foxpro_num' => $foxproHistory->nkarta,
+ 'archive_num' => $foxproHistory->narhiv,
+ 'post_in' => $foxproHistory->datearhiv,
+ 'mis_history_id' => $misHistory?->MedicalHistoryID,
+ 'mis_num' => $misHistory?->MedCardNum,
+ 'status_id' => 2, // Статус по умолчанию
+ ]);
+ }
+}
diff --git a/composer.json b/composer.json
index 3a5aceb..0919543 100644
--- a/composer.json
+++ b/composer.json
@@ -13,6 +13,7 @@
"laravel/tinker": "^2.10.1"
},
"require-dev": {
+ "barryvdh/laravel-debugbar": "^3.16",
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
diff --git a/composer.lock b/composer.lock
index e89e1ac..1d0eb00 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "4ecf76c8e987c4f4186a17e150248317",
+ "content-hash": "d35c2af4354943a6ab7e9b9ca3c4fed0",
"packages": [
{
"name": "brick/math",
@@ -6136,6 +6136,91 @@
}
],
"packages-dev": [
+ {
+ "name": "barryvdh/laravel-debugbar",
+ "version": "v3.16.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/barryvdh/laravel-debugbar.git",
+ "reference": "730dbf8bf41f5691e026dd771e64dd54ad1b10b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/730dbf8bf41f5691e026dd771e64dd54ad1b10b3",
+ "reference": "730dbf8bf41f5691e026dd771e64dd54ad1b10b3",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/routing": "^10|^11|^12",
+ "illuminate/session": "^10|^11|^12",
+ "illuminate/support": "^10|^11|^12",
+ "php": "^8.1",
+ "php-debugbar/php-debugbar": "^2.2.4",
+ "symfony/finder": "^6|^7"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.3.3",
+ "orchestra/testbench-dusk": "^7|^8|^9|^10",
+ "phpunit/phpunit": "^9.5.10|^10|^11",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
+ },
+ "providers": [
+ "Barryvdh\\Debugbar\\ServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-master": "3.16-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Barryvdh\\Debugbar\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "PHP Debugbar integration for Laravel",
+ "keywords": [
+ "debug",
+ "debugbar",
+ "dev",
+ "laravel",
+ "profiler",
+ "webprofiler"
+ ],
+ "support": {
+ "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
+ "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.2"
+ },
+ "funding": [
+ {
+ "url": "https://fruitcake.nl",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/barryvdh",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-03T14:52:46+00:00"
+ },
{
"name": "brianium/paratest",
"version": "v7.14.2",
@@ -7614,6 +7699,80 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
+ {
+ "name": "php-debugbar/php-debugbar",
+ "version": "v2.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-debugbar/php-debugbar.git",
+ "reference": "c5dce08e98dd101c771e55949fd89124b216271d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/c5dce08e98dd101c771e55949fd89124b216271d",
+ "reference": "c5dce08e98dd101c771e55949fd89124b216271d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1",
+ "psr/log": "^1|^2|^3",
+ "symfony/var-dumper": "^5.4|^6.4|^7.3|^8.0"
+ },
+ "replace": {
+ "maximebf/debugbar": "self.version"
+ },
+ "require-dev": {
+ "dbrekelmans/bdi": "^1",
+ "phpunit/phpunit": "^10",
+ "symfony/browser-kit": "^6.0|7.0",
+ "symfony/panther": "^1|^2.1",
+ "twig/twig": "^3.11.2"
+ },
+ "suggest": {
+ "kriswallsmith/assetic": "The best way to manage assets",
+ "monolog/monolog": "Log using Monolog",
+ "predis/predis": "Redis storage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "DebugBar\\": "src/DebugBar/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Maxime Bouroumeau-Fuseau",
+ "email": "maxime.bouroumeau@gmail.com",
+ "homepage": "http://maximebf.com"
+ },
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "Debug bar in the browser for php application",
+ "homepage": "https://github.com/php-debugbar/php-debugbar",
+ "keywords": [
+ "debug",
+ "debug bar",
+ "debugbar",
+ "dev"
+ ],
+ "support": {
+ "issues": "https://github.com/php-debugbar/php-debugbar/issues",
+ "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.5"
+ },
+ "time": "2025-12-21T08:50:08+00:00"
+ },
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
diff --git a/config/app.php b/config/app.php
index 423eed5..02d4ed4 100644
--- a/config/app.php
+++ b/config/app.php
@@ -65,7 +65,7 @@ return [
|
*/
- 'timezone' => 'UTC',
+ 'timezone' => 'Asia/Yakutsk',
/*
|--------------------------------------------------------------------------
diff --git a/database/migrations/2025_11_30_112144_create_archive_histories_table.php b/database/migrations/2025_11_30_112144_create_archive_histories_table.php
index 3ef879f..baf0a5f 100644
--- a/database/migrations/2025_11_30_112144_create_archive_histories_table.php
+++ b/database/migrations/2025_11_30_112144_create_archive_histories_table.php
@@ -13,7 +13,9 @@ return new class extends Migration
{
Schema::create('archive_histories', function (Blueprint $table) {
$table->id();
- $table->morphs('historyable');
+ $table->string('keyarhiv')->nullable();
+ $table->string('foxpro_history_id')->nullable();
+ $table->bigInteger('mis_history_id')->nullable();
$table->timestamp('issue_at');
$table->timestamp('return_at')->nullable();
$table->text('comment')->nullable();
diff --git a/database/migrations/2025_12_05_030609_create_archive_infos_table.php b/database/migrations/2025_12_05_030609_create_archive_infos_table.php
index 71122f0..7debc34 100644
--- a/database/migrations/2025_12_05_030609_create_archive_infos_table.php
+++ b/database/migrations/2025_12_05_030609_create_archive_infos_table.php
@@ -13,8 +13,11 @@ return new class extends Migration
{
Schema::create('archive_infos', function (Blueprint $table) {
$table->id();
- $table->morphs('historyable');
- $table->string('num');
+ $table->string('foxpro_history_id')->nullable();
+ $table->bigInteger('mis_history_id')->nullable();
+ $table->string('foxpro_num')->nullable();
+ $table->string('mis_num')->nullable();
+ $table->string('archive_num');
$table->date('post_in');
$table->foreignIdFor(\App\Models\ArchiveStatus::class, 'status_id')->default(1);
$table->timestamps();
diff --git a/database/seeders/OrgSeeder.php b/database/seeders/OrgSeeder.php
new file mode 100644
index 0000000..1b06331
--- /dev/null
+++ b/database/seeders/OrgSeeder.php
@@ -0,0 +1,21 @@
+ '0',
+ 'name' => 'Неизвестно'
+ ]);
+ }
+}
diff --git a/resources/js/Composables/useMedicalHistoryFilter.js b/resources/js/Composables/useMedicalHistoryFilter.js
index f95a127..a4387d5 100644
--- a/resources/js/Composables/useMedicalHistoryFilter.js
+++ b/resources/js/Composables/useMedicalHistoryFilter.js
@@ -16,51 +16,15 @@ export const useMedicalHistoryFilter = (initialFilters = {}) => {
page_size: initialFilters?.page_size || 50,
sort_by: initialFilters?.sort_by || 'dateextract',
sort_order: initialFilters?.sort_order || 'desc',
- view_type: initialFilters?.view_type || 'si',
status: initialFilters?.status || null,
- database: initialFilters?.database || 'separate', // НОВЫЙ ПАРАМЕТР: postgresql, mssql, smart, separate
})
// Метаданные для разных типов данных
const meta = computed(() => {
- const cards = page.props.cards
-
- // Для раздельного поиска
- if (filtersRef.value.database === 'separate') {
- return {
- si: cards.si.meta || {},
- mis: cards.mis.meta || {},
- stats: cards.stats || {},
- }
- } else if (filtersRef.value.database === 'mis') {
- return {
- mis: cards.mis?.meta
- }
- } else {
- return {
- si: cards.si?.meta
- }
- }
+ return page.props.cards.meta
})
const isLoading = ref(false)
- const databaseStats = computed(() => page.props.databaseStats || {})
-
- // Статистика по базам
- const databaseInfo = computed(() => ({
- postgresql: {
- count: databaseStats.value.postgresql?.total || 0,
- label: 'PostgreSQL',
- color: 'blue',
- description: 'Основной архив'
- },
- mssql: {
- count: databaseStats.value.mssql?.total || 0,
- label: 'MSSQL',
- color: 'purple',
- description: 'Исторический архив'
- }
- }))
// Форматирование даты для URL
const formatDateForUrl = (date) => {
@@ -81,8 +45,6 @@ export const useMedicalHistoryFilter = (initialFilters = {}) => {
page_size: filtersRef.value.page_size,
sort_by: filtersRef.value.sort_by,
sort_order: filtersRef.value.sort_order,
- view_type: filtersRef.value.view_type,
- database: filtersRef.value.database,
status: filtersRef.value.status
}
@@ -128,10 +90,6 @@ export const useMedicalHistoryFilter = (initialFilters = {}) => {
debouncedSearch(value)
}
- const handleDatabaseChange = (database) => {
- applyFilters({ database, page: 1 }, false)
- }
-
// Конвертация строки даты в timestamp для NaiveUI
const convertToTimestamp = (dateString) => {
if (!dateString) return null
@@ -186,10 +144,6 @@ export const useMedicalHistoryFilter = (initialFilters = {}) => {
applyFilters({ page_size: size, page: 1 })
}
- const handleViewTypeChange = (view_type) => {
- applyFilters({ view_type, page: 1 })
- }
-
const handleSortChange = (sorter) => {
applyFilters({
sort_by: sorter.columnKey,
@@ -213,8 +167,6 @@ export const useMedicalHistoryFilter = (initialFilters = {}) => {
page_size: 15,
sort_by: 'dateextract',
sort_order: 'desc',
- view_type: 'archive',
- database: 'postgresql',
}
dateRange.value = [null, null]
applyFilters({}, true)
@@ -270,22 +222,6 @@ export const useMedicalHistoryFilter = (initialFilters = {}) => {
})
}
- // Фильтр по базе данных
- if (filtersRef.value.database) {
- const dbLabel = {
- postgresql: 'PostgreSQL',
- mssql: 'MSSQL',
- smart: 'Умный поиск',
- separate: 'Раздельно'
- }[filtersRef.value.database] || filtersRef.value.database
-
- active.push({
- key: 'database',
- label: `База: ${dbLabel}`,
- icon: 'database'
- })
- }
-
return active
})
@@ -341,12 +277,8 @@ export const useMedicalHistoryFilter = (initialFilters = {}) => {
activeFilters,
dateRange,
searchValue,
- databaseInfo,
- databaseStats,
// Обработчики
- handleDatabaseChange,
- handleViewTypeChange,
handleSearch,
handleDateRangeChange,
handlePageChange,
diff --git a/resources/js/Layouts/AppLayout.vue b/resources/js/Layouts/AppLayout.vue
index ce12593..c9e0bbd 100644
--- a/resources/js/Layouts/AppLayout.vue
+++ b/resources/js/Layouts/AppLayout.vue
@@ -1,5 +1,5 @@
-
+
diff --git a/resources/js/Pages/Home/ArchiveHistoryModal/Index.vue b/resources/js/Pages/Home/ArchiveHistoryModal/Index.vue
index bc6aacf..785e883 100644
--- a/resources/js/Pages/Home/ArchiveHistoryModal/Index.vue
+++ b/resources/js/Pages/Home/ArchiveHistoryModal/Index.vue
@@ -7,7 +7,7 @@ import {useNotification} from "../../../Composables/useNotification.js";
const open = defineModel('open')
const props = defineProps({
- patientId: {
+ patientInfo: {
type: Number,
}
})
@@ -18,10 +18,10 @@ const loading = ref(true)
const patient = ref({})
const showArchiveHistoryModal = ref(false)
const selectedArchiveHistoryId = ref(null)
-const selectedArchiveHistoryType = ref(null)
+const selectedArchiveHistoryType = ref(props.patientInfo.type)
const isCreateNewArchiveHistoryModal = ref(false)
const archiveInfo = ref({
- id: props.patientId,
+ id: props.patientInfo.id,
num: null,
post_in: null,
status: null
@@ -30,7 +30,7 @@ const emits = defineEmits(['historyUpdated', 'closeWithoutSave'])
const onResetData = () => {
archiveInfo.value = {
- id: props.patientId,
+ id: props.patientInfo.id,
num: null,
post_in: null,
status: null
@@ -47,16 +47,16 @@ const patientData = ref({
})
const loadPatientData = async () => {
- if (!props.patientId) return
+ if (!props.patientInfo.id) return
loading.value = true
try {
- await axios.get(`/api/si/patients/${props.patientId}?view_type=${filtersRef.value.view_type}`).then(res => {
+ await axios.get(`/api/si/patients/${props.patientInfo.id}?view_type=${props.patientInfo.type}`).then(res => {
patient.value = res.data
patientData.value = res.data.info
archiveInfo.value = res.data.archiveInfo ?? {
- historyable_type: res.data.historyable_type,
- id: props.patientId,
+ // historyable_type: res.data.historyable_type,
+ id: props.patientInfo.id,
num: null,
post_in: null,
status: null,
@@ -73,12 +73,12 @@ const loadPatientData = async () => {
const onShowArchiveHistoryModal = (id) => {
if (id === null) {
isCreateNewArchiveHistoryModal.value = true
- selectedArchiveHistoryId.value = props.patientId
+ selectedArchiveHistoryId.value = props.patientInfo.id
} else {
isCreateNewArchiveHistoryModal.value = false
selectedArchiveHistoryId.value = id
}
- selectedArchiveHistoryType.value = archiveInfo.value.historyable_type
+ selectedArchiveHistoryType.value = props.patientInfo.type
showArchiveHistoryModal.value = true
}
@@ -122,7 +122,7 @@ const onUpdateHistory = async ({data}) => {
}
emits('historyUpdated', {
data: updatedData,
- patientId: props.patientId
+ patientId: props.patientInfo.id
})
}
@@ -130,14 +130,14 @@ const hasCreateNew = computed(() => archiveInfo.value === null)
const onSubmit = async () => {
try {
- await axios.post(`/api/archive/histories/info/${props.patientId}`, archiveInfo.value).then(res => {
+ await axios.post(`/api/archive/histories/info/${props.patientInfo.id}`, archiveInfo.value).then(res => {
// onCloseWithoutSave()
const updatedData = {
status: archiveInfo.value.status,
}
emits('historyUpdated', {
data: updatedData,
- patientId: props.patientId
+ patientId: props.patientInfo.id
})
open.value = false
})
@@ -151,7 +151,7 @@ const onSubmit = async () => {
}
// Наблюдаем за изменением patientId
-watch(() => props.patientId, async (newId) => {
+watch(() => props.patientInfo, async (newId) => {
if (newId) {
await loadPatientData()
}
@@ -177,6 +177,11 @@ watch(() => props.patientId, async (newId) => {
+
+
+ {{ archiveInfo.mis_num }} / {{ archiveInfo.foxpro_num }}
+
+
@@ -202,7 +207,7 @@ watch(() => props.patientId, async (newId) => {
-
+