Много всего
This commit is contained in:
@@ -27,6 +27,12 @@ DB_CONNECTION=sqlite
|
|||||||
# DB_USERNAME=root
|
# DB_USERNAME=root
|
||||||
# DB_PASSWORD=
|
# DB_PASSWORD=
|
||||||
|
|
||||||
|
MIS_DB_HOST=
|
||||||
|
MIS_DB_PORT=
|
||||||
|
MIS_DB_DATABASE=
|
||||||
|
MIS_DB_USERNAME=
|
||||||
|
MIS_DB_PASSWORD=
|
||||||
|
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
SESSION_ENCRYPT=false
|
SESSION_ENCRYPT=false
|
||||||
|
|||||||
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;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Resources\ArchiveHistoryResource;
|
use App\Http\Resources\ArchiveHistoryResource;
|
||||||
|
use App\Http\Resources\ArchiveInfoResource;
|
||||||
use App\Models\ArchiveHistory;
|
use App\Models\ArchiveHistory;
|
||||||
|
use App\Models\ArchiveInfo;
|
||||||
|
use App\Rules\DateTimeOrStringOrNumber;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
@@ -21,22 +24,20 @@ class ArchiveHistoryController extends Controller
|
|||||||
return response()->json($archiveHistory);
|
return response()->json($archiveHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function move(Request $request)
|
public function moveStore(Request $request)
|
||||||
{
|
{
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'issue_at' => 'nullable|numeric:',
|
'issue_at' => ['nullable', new DateTimeOrStringOrNumber],
|
||||||
'return_at' => 'nullable|numeric:',
|
'return_at' => ['nullable', new DateTimeOrStringOrNumber],
|
||||||
'org_id' => 'required|numeric',
|
'org_id' => 'required|numeric',
|
||||||
'employee_name' => 'nullable|string',
|
'employee_name' => 'nullable|string',
|
||||||
'employee_post' => 'nullable|string',
|
'employee_post' => 'nullable|string',
|
||||||
'comment' => 'nullable|string',
|
'comment' => 'nullable|string',
|
||||||
'has_lost' => 'boolean',
|
'has_lost' => 'boolean',
|
||||||
'historyable_id' => 'nullable|numeric',
|
'historyable_id' => 'nullable|numeric',
|
||||||
|
'historyable_type' => 'required|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Находим связанную модель ??
|
|
||||||
$historyable = SttMedicalHistory::findOrFail($data['historyable_id']);
|
|
||||||
|
|
||||||
// Преобразуем timestamp в дату, если пришли числа
|
// Преобразуем timestamp в дату, если пришли числа
|
||||||
if (isset($data['issue_at']) && is_numeric($data['issue_at'])) {
|
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'])->format('Y-m-d');
|
||||||
@@ -48,6 +49,89 @@ class ArchiveHistoryController extends Controller
|
|||||||
|
|
||||||
$archiveHistory = ArchiveHistory::create($data);
|
$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));
|
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;
|
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\Models\SI\SttMedicalHistory;
|
||||||
|
use App\Repositories\MedicalHistoryRepository;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class IndexController extends Controller
|
class IndexController extends Controller
|
||||||
{
|
{
|
||||||
|
private MedicalHistoryRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(MedicalHistoryRepository $repository)
|
||||||
|
{
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$pageSize = $request->get('page_size', 15);
|
$pageSize = $request->get('page_size', 50);
|
||||||
$searchText = $request->get('search', null);
|
$searchText = $request->get('search', null);
|
||||||
$dateExtractFrom = $request->get('date_extract_from', null);
|
$dateExtractFrom = $request->get('date_extract_from', null);
|
||||||
$dateExtractTo = $request->get('date_extract_to', 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)) {
|
switch ($database) {
|
||||||
$cardsQuery = $cardsQuery->search($searchText);
|
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)) {
|
$statuses = ArchiveStatus::all()->map(function ($status) {
|
||||||
$cardsQuery = $cardsQuery->whereDate('dateextract', '>=', $dateExtractFrom);
|
return [
|
||||||
if (!empty($dateExtractTo)) {
|
'value' => $status->id,
|
||||||
$cardsQuery = $cardsQuery->whereDate('dateextract', '<=', $dateExtractTo);
|
'label' => $status->text
|
||||||
}
|
];
|
||||||
}
|
});
|
||||||
|
|
||||||
$cards = SttMedicalHistoryResource::collection($cardsQuery->paginate($pageSize));
|
|
||||||
|
|
||||||
return Inertia::render('Home/Index', [
|
return Inertia::render('Home/Index', [
|
||||||
'cards' => $cards,
|
'cards' => $data,
|
||||||
'filters' => $request->only([
|
'statuses' => $statuses,
|
||||||
'search', 'date_extract_from', 'date_extract_to', 'page_size', 'page', 'view_type'
|
'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\ArchiveHistoryResource;
|
||||||
use App\Http\Resources\ArchiveInfoResource;
|
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;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class MedicalHistoryController extends Controller
|
class MedicalHistoryController extends Controller
|
||||||
@@ -15,22 +18,27 @@ class MedicalHistoryController extends Controller
|
|||||||
$patientId = $request->get('patient_id');
|
$patientId = $request->get('patient_id');
|
||||||
|
|
||||||
$patientInfo = null;
|
$patientInfo = null;
|
||||||
if ($viewType == 'si') {
|
if ($viewType == 'si') $patient = SiSttMedicalHistory::where('id', $id)->first();
|
||||||
$patient = SttMedicalHistory::where('id', $id)->first();
|
else $patient = MisSttMedicalHistory::where('MedicalHistoryID', $id)->first();
|
||||||
$archiveJournal = ArchiveHistoryResource::collection($patient->archiveHistory);
|
$archiveJournal = $patient->archiveHistory ? ArchiveHistoryResource::collection($patient->archiveHistory) : null;
|
||||||
$archiveInfo = $patient->archiveInfo;
|
|
||||||
|
|
||||||
if (!empty($archiveInfo)) {
|
if (!empty($patient->archiveInfo)) {
|
||||||
$archiveInfo = ArchiveInfoResource::make($patient->archiveInfo);
|
$archiveInfo = ArchiveInfoResource::make($patient->archiveInfo)->toArray(request());
|
||||||
}
|
} else {
|
||||||
|
$archiveInfo = null;
|
||||||
$patientInfo = [
|
|
||||||
'info' => $patient,
|
|
||||||
'journal' => $archiveJournal,
|
|
||||||
'archiveInfo' => $archiveInfo,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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);
|
return response()->json($patientInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class ArchiveHistoryResource extends JsonResource
|
|||||||
return [
|
return [
|
||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'issue_at' => Carbon::parse($this->issue_at)->format('d.m.Y'),
|
'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,
|
'comment' => $this->comment,
|
||||||
'org_id' => $this->org_id,
|
'org_id' => $this->org_id,
|
||||||
'org' => $this->org->name,
|
'org' => $this->org->name,
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ class ArchiveInfoResource extends JsonResource
|
|||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'num' => $this->num,
|
'num' => $this->num,
|
||||||
'post_in' => $this->post_in,
|
'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'),
|
'dateextract' => Carbon::parse($this->dateextract)->format('d.m.Y'),
|
||||||
'card_num' => $this->archiveInfo->num ?? null,
|
'card_num' => $this->archiveInfo->num ?? null,
|
||||||
'status' => $this->archiveInfo->status ?? null,
|
'status' => $this->archiveInfo->status ?? null,
|
||||||
'datearhiv' => Carbon::parse($this->datearhiv)->format('d.m.Y'),
|
'datearhiv' => $this->archiveInfo?->post_at ? Carbon::parse($this->archiveInfo->post_at)->format('d.m.Y') : null,
|
||||||
'statgod' => $this->statgod,
|
|
||||||
'enp' => $this->enp,
|
|
||||||
'medcardnum' => $this->medcardnum,
|
'medcardnum' => $this->medcardnum,
|
||||||
'dr' => Carbon::parse($this->dr)->format('d.m.Y'),
|
'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
|
class ArchiveHistory extends Model
|
||||||
{
|
{
|
||||||
|
protected $connection = 'pgsql';
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::created(function ($archiveHistory) {
|
static::created(function ($archiveHistory) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class ArchiveInfo extends Model
|
class ArchiveInfo extends Model
|
||||||
{
|
{
|
||||||
|
protected $connection = 'pgsql';
|
||||||
|
protected $table = 'archive_infos';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'historyable_type',
|
'historyable_type',
|
||||||
'historyable_id',
|
'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
|
class SttMedicalHistory extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'si_stt_patients';
|
protected $table = 'si_stt_patients';
|
||||||
|
protected $connection = 'pgsql';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'family', // Фамилия
|
'family', // Фамилия
|
||||||
@@ -39,6 +40,34 @@ class SttMedicalHistory extends Model
|
|||||||
return $this->morphOne(ArchiveInfo::class, 'historyable');
|
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)
|
public function scopeSearch($query, $searchText)
|
||||||
{
|
{
|
||||||
return $query->where(function($q) use ($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('Ошибка при проверки даты');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,20 @@ return [
|
|||||||
|
|
||||||
'connections' => [
|
'connections' => [
|
||||||
|
|
||||||
|
'mis' => [
|
||||||
|
'driver' => 'pgsql',
|
||||||
|
'host' => env('MIS_DB_HOST', 'localhost'),
|
||||||
|
'port' => env('MIS_DB_PORT', '5432'),
|
||||||
|
'database' => env('MIS_DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('MIS_DB_USERNAME', 'root'),
|
||||||
|
'password' => env('MIS_DB_PASSWORD', ''),
|
||||||
|
'charset' => env('MIS_DB_CHARSET', 'utf8'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'search_path' => 'public',
|
||||||
|
'sslmode' => 'prefer',
|
||||||
|
],
|
||||||
|
|
||||||
'sqlite' => [
|
'sqlite' => [
|
||||||
'driver' => 'sqlite',
|
'driver' => 'sqlite',
|
||||||
'url' => env('DB_URL'),
|
'url' => env('DB_URL'),
|
||||||
|
|||||||
@@ -27,5 +27,10 @@ class ArchiveStatusSeeder extends Seeder
|
|||||||
'text' => 'Выдана',
|
'text' => 'Выдана',
|
||||||
'variant' => 'warning'
|
'variant' => 'warning'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ArchiveStatus::create([
|
||||||
|
'text' => 'Утеряна',
|
||||||
|
'variant' => 'error'
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,66 @@
|
|||||||
// composables/useMedicalHistoryFilter.js
|
// composables/useMedicalHistoryFilter.js
|
||||||
import { router, usePage } from '@inertiajs/vue3'
|
import { ref, computed, watch } from 'vue'
|
||||||
import {ref, computed, watch} from 'vue'
|
import { usePage, router } from '@inertiajs/vue3'
|
||||||
import { stringifyQuery } from 'ufo'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import {format, isValid, parse, parseISO} from 'date-fns'
|
import { format, parse, isValid, parseISO } from 'date-fns'
|
||||||
import {useDebounceFn} from "@vueuse/core";
|
|
||||||
|
|
||||||
export const useMedicalHistoryFilter = (filters) => {
|
export const useMedicalHistoryFilter = (initialFilters = {}) => {
|
||||||
const page = usePage()
|
const page = usePage()
|
||||||
|
|
||||||
// Реактивные фильтры с начальными значениями из URL
|
// Реактивные фильтры с начальными значениями из URL
|
||||||
const filtersRef = ref({
|
const filtersRef = ref({
|
||||||
search: filters?.search || '',
|
search: initialFilters?.search || '',
|
||||||
date_extract_from: filters?.date_extract_from || null,
|
date_extract_from: initialFilters?.date_extract_from || null,
|
||||||
date_extract_to: filters?.date_extract_to || null,
|
date_extract_to: initialFilters?.date_extract_to || null,
|
||||||
page: filters?.page || 1,
|
page: initialFilters?.page || 1,
|
||||||
page_size: filters?.page_size || 15,
|
page_size: initialFilters?.page_size || 50,
|
||||||
sort_by: filters?.sort_by || 'date_extract',
|
sort_by: initialFilters?.sort_by || 'dateextract',
|
||||||
sort_order: filters?.sort_order || 'desc',
|
sort_order: initialFilters?.sort_order || 'desc',
|
||||||
view_type: filters?.view_type || 'archive'
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const meta = computed(() => page.props.cards?.meta || {})
|
|
||||||
const isLoading = ref(false)
|
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
|
// Форматирование даты для URL
|
||||||
const formatDateForUrl = (date) => {
|
const formatDateForUrl = (date) => {
|
||||||
@@ -32,17 +71,8 @@ export const useMedicalHistoryFilter = (filters) => {
|
|||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
// Навигация с фильтрами
|
// Подготовка параметров запроса
|
||||||
const applyFilters = (updates = {}, resetPage = false) => {
|
const prepareParams = () => {
|
||||||
// Обновляем фильтры
|
|
||||||
Object.assign(filtersRef.value, updates)
|
|
||||||
|
|
||||||
// Если сбрасываем фильтры, обнуляем страницу
|
|
||||||
if (resetPage) {
|
|
||||||
filtersRef.value.page = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Подготавливаем параметры для URL
|
|
||||||
const params = {
|
const params = {
|
||||||
search: filtersRef.value.search || null,
|
search: filtersRef.value.search || null,
|
||||||
date_extract_from: formatDateForUrl(filtersRef.value.date_extract_from),
|
date_extract_from: formatDateForUrl(filtersRef.value.date_extract_from),
|
||||||
@@ -52,19 +82,33 @@ export const useMedicalHistoryFilter = (filters) => {
|
|||||||
sort_by: filtersRef.value.sort_by,
|
sort_by: filtersRef.value.sort_by,
|
||||||
sort_order: filtersRef.value.sort_order,
|
sort_order: filtersRef.value.sort_order,
|
||||||
view_type: filtersRef.value.view_type,
|
view_type: filtersRef.value.view_type,
|
||||||
|
database: filtersRef.value.database,
|
||||||
|
status: filtersRef.value.status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Очищаем пустые значения
|
// Очищаем пустые значения
|
||||||
const cleanParams = Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(params).filter(([_, value]) =>
|
Object.entries(params).filter(([_, value]) =>
|
||||||
value !== undefined && value !== null && value !== ''
|
value !== undefined && value !== null && value !== ''
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const query = stringifyQuery(cleanParams)
|
// Навигация с фильтрами
|
||||||
|
const applyFilters = (updates = {}, resetPage = true) => {
|
||||||
|
// Обновляем фильтры
|
||||||
|
Object.assign(filtersRef.value, updates)
|
||||||
|
|
||||||
|
// Если сбрасываем фильтры, обнуляем страницу
|
||||||
|
if (resetPage) {
|
||||||
|
filtersRef.value.page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = prepareParams()
|
||||||
|
const query = new URLSearchParams(params).toString()
|
||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
router.visit(`/${query ? `?${query}` : ''}`, {
|
router.get(`/${query ? `?${query}` : ''}`, params, {
|
||||||
preserveState: true,
|
preserveState: true,
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
onFinish: () => {
|
onFinish: () => {
|
||||||
@@ -84,6 +128,10 @@ export const useMedicalHistoryFilter = (filters) => {
|
|||||||
debouncedSearch(value)
|
debouncedSearch(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDatabaseChange = (database) => {
|
||||||
|
applyFilters({ database, page: 1 }, false)
|
||||||
|
}
|
||||||
|
|
||||||
// Конвертация строки даты в timestamp для NaiveUI
|
// Конвертация строки даты в timestamp для NaiveUI
|
||||||
const convertToTimestamp = (dateString) => {
|
const convertToTimestamp = (dateString) => {
|
||||||
if (!dateString) return null
|
if (!dateString) return null
|
||||||
@@ -128,12 +176,8 @@ export const useMedicalHistoryFilter = (filters) => {
|
|||||||
applyFilters(updates, true)
|
applyFilters(updates, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStatusChange = (status) => {
|
const handlePageChange = (pageNum) => {
|
||||||
applyFilters({ status }, true)
|
applyFilters({ page: pageNum }, false)
|
||||||
}
|
|
||||||
|
|
||||||
const handlePageChange = (page) => {
|
|
||||||
applyFilters({ page })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePageSizeChange = (size) => {
|
const handlePageSizeChange = (size) => {
|
||||||
@@ -148,7 +192,14 @@ export const useMedicalHistoryFilter = (filters) => {
|
|||||||
applyFilters({
|
applyFilters({
|
||||||
sort_by: sorter.columnKey,
|
sort_by: sorter.columnKey,
|
||||||
sort_order: sorter.order
|
sort_order: sorter.order
|
||||||
})
|
}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleStatusChange = (status_id) => {
|
||||||
|
applyFilters({
|
||||||
|
status: status_id
|
||||||
|
}, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetAllFilters = () => {
|
const resetAllFilters = () => {
|
||||||
@@ -158,34 +209,112 @@ export const useMedicalHistoryFilter = (filters) => {
|
|||||||
date_extract_to: null,
|
date_extract_to: null,
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
sort_by: 'created_at',
|
sort_by: 'dateextract',
|
||||||
sort_order: 'desc',
|
sort_order: 'desc',
|
||||||
view_type: 'archive',
|
view_type: 'archive',
|
||||||
|
database: 'postgresql',
|
||||||
}
|
}
|
||||||
|
dateRange.value = [null, null]
|
||||||
applyFilters({}, true)
|
applyFilters({}, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Быстрый поиск по типу (для тулбара)
|
||||||
|
const quickSearch = (type, value) => {
|
||||||
|
const params = {
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
database: filtersRef.value.database,
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/quick-search', params, {
|
||||||
|
preserveState: true,
|
||||||
|
preserveScroll: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Приоритетный поиск
|
||||||
|
const prioritySearch = (searchTerm) => {
|
||||||
|
router.get('/priority-search', {
|
||||||
|
q: searchTerm
|
||||||
|
}, {
|
||||||
|
preserveState: true,
|
||||||
|
preserveScroll: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Активные фильтры для отображения
|
// Активные фильтры для отображения
|
||||||
const activeFilters = computed(() => {
|
const activeFilters = computed(() => {
|
||||||
const active = []
|
const active = []
|
||||||
|
|
||||||
if (filtersRef.value.search) {
|
if (filtersRef.value.search) {
|
||||||
active.push({ key: 'search', label: `Поиск: ${filtersRef.value.search}` })
|
active.push({
|
||||||
|
key: 'search',
|
||||||
|
label: `Поиск: ${filtersRef.value.search}`,
|
||||||
|
icon: 'search'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filtersRef.value.date_extract_from || filtersRef.value.date_extract_to) {
|
if (filtersRef.value.date_extract_from || filtersRef.value.date_extract_to) {
|
||||||
const from = filtersRef.value.date_extract_from ? format(parseISO(filtersRef.value.date_extract_from), 'dd.MM.yyyy') : ''
|
const from = filtersRef.value.date_extract_from
|
||||||
const to = filtersRef.value.date_extract_to ? format(parseISO(filtersRef.value.date_extract_to), 'dd.MM.yyyy') : ''
|
? format(parseISO(filtersRef.value.date_extract_from), 'dd.MM.yyyy')
|
||||||
active.push({ key: 'date', label: `Дата: ${from} - ${to}` })
|
: ''
|
||||||
|
const to = filtersRef.value.date_extract_to
|
||||||
|
? format(parseISO(filtersRef.value.date_extract_to), 'dd.MM.yyyy')
|
||||||
|
: ''
|
||||||
|
active.push({
|
||||||
|
key: 'date',
|
||||||
|
label: `Дата выписки: ${from} ${to ? '- ' + to : ''}`,
|
||||||
|
icon: 'calendar'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// Добавьте другие фильтры по необходимости
|
|
||||||
|
// Фильтр по базе данных
|
||||||
|
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
|
return active
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Удаление конкретного фильтра
|
||||||
|
const removeFilter = (key) => {
|
||||||
|
const updates = {}
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'search':
|
||||||
|
updates.search = ''
|
||||||
|
break
|
||||||
|
case 'date':
|
||||||
|
updates.date_extract_from = null
|
||||||
|
updates.date_extract_to = null
|
||||||
|
dateRange.value = [null, null]
|
||||||
|
break
|
||||||
|
case 'database':
|
||||||
|
updates.database = 'postgresql'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(filtersRef.value, updates)
|
||||||
|
applyFilters(updates, true)
|
||||||
|
}
|
||||||
|
|
||||||
// Следим за изменениями в page.props.filters
|
// Следим за изменениями в page.props.filters
|
||||||
watch(
|
watch(
|
||||||
() => page.props.filters,
|
() => page.props.filters,
|
||||||
(newFilters) => {
|
(newFilters) => {
|
||||||
if (newFilters) {
|
if (newFilters) {
|
||||||
// Сохраняем все фильтры, включая search!
|
// Сохраняем все фильтры
|
||||||
filtersRef.value = {
|
filtersRef.value = {
|
||||||
...filtersRef.value,
|
...filtersRef.value,
|
||||||
...newFilters
|
...newFilters
|
||||||
@@ -209,14 +338,22 @@ export const useMedicalHistoryFilter = (filters) => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
activeFilters,
|
activeFilters,
|
||||||
dateRange,
|
dateRange,
|
||||||
|
databaseInfo,
|
||||||
|
databaseStats,
|
||||||
|
|
||||||
|
// Обработчики
|
||||||
|
handleDatabaseChange,
|
||||||
handleViewTypeChange,
|
handleViewTypeChange,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
handleDateRangeChange,
|
handleDateRangeChange,
|
||||||
handleStatusChange,
|
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePageSizeChange,
|
handlePageSizeChange,
|
||||||
handleSortChange,
|
handleSortChange,
|
||||||
|
handleStatusChange,
|
||||||
resetAllFilters,
|
resetAllFilters,
|
||||||
applyFilters
|
removeFilter,
|
||||||
|
applyFilters,
|
||||||
|
quickSearch,
|
||||||
|
prioritySearch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,17 @@ const themeOverrides = {
|
|||||||
<template>
|
<template>
|
||||||
<NConfigProvider :theme-overrides="themeOverrides" :locale="ruRU" :date-locale="dateRuRU">
|
<NConfigProvider :theme-overrides="themeOverrides" :locale="ruRU" :date-locale="dateRuRU">
|
||||||
<NLayout class="h-screen">
|
<NLayout class="h-screen">
|
||||||
<NLayout position="absolute" has-sider>
|
<NLayout position="absolute" content-class="p-6 relative" :native-scrollbar="false">
|
||||||
<NLayoutSider
|
<!-- <NLayoutSider-->
|
||||||
:native-scrollbar="false"
|
<!-- :native-scrollbar="false"-->
|
||||||
bordered
|
<!-- bordered-->
|
||||||
>
|
<!-- >-->
|
||||||
<SideMenu />
|
<!-- <SideMenu />-->
|
||||||
</NLayoutSider>
|
<!-- </NLayoutSider>-->
|
||||||
<NLayout content-class="p-6 relative" :native-scrollbar="false">
|
<div>
|
||||||
<div>
|
<slot name="header" />
|
||||||
<slot name="header" />
|
</div>
|
||||||
</div>
|
<slot />
|
||||||
<slot />
|
|
||||||
</NLayout>
|
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</NConfigProvider>
|
</NConfigProvider>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NModal, NDataTable, NSpace, NFlex, NButton, NForm, NFormItem, NInput, NDatePicker, NDivider, NSwitch, NTag } from 'naive-ui'
|
import { NModal, NDataTable, NSpace, NFlex, NButton, NForm, NFormItem, NInput, NDatePicker, NDivider, NSwitch, NTag } from 'naive-ui'
|
||||||
import ArchiveHistoryMoveModal from '../ArchiveHistoryMoveModal/Index.vue'
|
import ArchiveHistoryMoveModal from '../ArchiveHistoryMoveModal/Index.vue'
|
||||||
import {ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
|
import {useMedicalHistoryFilter} from "../../../Composables/useMedicalHistoryFilter.js";
|
||||||
|
|
||||||
const open = defineModel('open')
|
const open = defineModel('open')
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -10,10 +11,29 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {filtersRef} = useMedicalHistoryFilter()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const patient = ref({})
|
const patient = ref({})
|
||||||
const showArchiveHistoryModal = ref(false)
|
const showArchiveHistoryModal = ref(false)
|
||||||
const selectedArchiveHistoryId = ref(null)
|
const selectedArchiveHistoryId = ref(null)
|
||||||
|
const selectedArchiveHistoryType = ref(null)
|
||||||
|
const isCreateNewArchiveHistoryModal = ref(false)
|
||||||
|
const archiveInfo = ref({
|
||||||
|
id: props.patientId,
|
||||||
|
num: null,
|
||||||
|
post_in: null,
|
||||||
|
status: null
|
||||||
|
})
|
||||||
|
const emits = defineEmits(['historyUpdated'])
|
||||||
|
|
||||||
|
const onResetData = () => {
|
||||||
|
archiveInfo.value = {
|
||||||
|
id: props.patientId,
|
||||||
|
num: null,
|
||||||
|
post_in: null,
|
||||||
|
status: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const patientData = ref({
|
const patientData = ref({
|
||||||
@@ -25,9 +45,16 @@ const loadPatientData = async () => {
|
|||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
axios.get(`/api/si/patients/${props.patientId}`).then(res => {
|
axios.get(`/api/si/patients/${props.patientId}?view_type=${filtersRef.value.view_type}`).then(res => {
|
||||||
patient.value = res.data
|
patient.value = res.data
|
||||||
patientData.value = res.data.info
|
patientData.value = res.data.info
|
||||||
|
archiveInfo.value = res.data.archiveInfo ?? {
|
||||||
|
historyable_type: res.data.historyable_type,
|
||||||
|
id: props.patientId,
|
||||||
|
num: null,
|
||||||
|
post_in: null,
|
||||||
|
status: null,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// message.error('Ошибка при загрузке данных пациента')
|
// message.error('Ошибка при загрузке данных пациента')
|
||||||
@@ -38,7 +65,14 @@ const loadPatientData = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onShowArchiveHistoryModal = (id) => {
|
const onShowArchiveHistoryModal = (id) => {
|
||||||
selectedArchiveHistoryId.value = id
|
if (id === null) {
|
||||||
|
isCreateNewArchiveHistoryModal.value = true
|
||||||
|
selectedArchiveHistoryId.value = props.patientId
|
||||||
|
} else {
|
||||||
|
isCreateNewArchiveHistoryModal.value = false
|
||||||
|
selectedArchiveHistoryId.value = id
|
||||||
|
}
|
||||||
|
selectedArchiveHistoryType.value = archiveInfo.value.historyable_type
|
||||||
showArchiveHistoryModal.value = true
|
showArchiveHistoryModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +109,30 @@ const rowProps = (row) => ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onUpdateHistory = (updatedData) => {
|
||||||
|
loadPatientData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCreateNew = computed(() => archiveInfo.value === null)
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await axios.post(`/api/archive/histories/info/${props.patientId}`, archiveInfo.value).then(res => {
|
||||||
|
// onCloseWithoutSave()
|
||||||
|
emits('historyUpdated', {
|
||||||
|
type: hasCreateNew.value ? 'created' : 'updated',
|
||||||
|
data: res.data,
|
||||||
|
patientId: props.patientId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Наблюдаем за изменением patientId
|
// Наблюдаем за изменением patientId
|
||||||
watch(() => props.patientId, (newId) => {
|
watch(() => props.patientId, (newId) => {
|
||||||
if (newId) {
|
if (newId) {
|
||||||
@@ -92,19 +150,19 @@ watch(() => props.patientId, (newId) => {
|
|||||||
<NFlex inline justify="space-between" align="center" :wrap="false">
|
<NFlex inline justify="space-between" align="center" :wrap="false">
|
||||||
<NForm inline :show-feedback="false">
|
<NForm inline :show-feedback="false">
|
||||||
<NFormItem label="Статус карты">
|
<NFormItem label="Статус карты">
|
||||||
<NTag :type="patient.archiveInfo?.status?.variant ?? 'error'" :bordered="false" round>
|
<NTag :type="archiveInfo.status?.variant ?? 'error'" :bordered="false" round>
|
||||||
{{ patient.archiveInfo?.status?.text ?? 'Нет в архиве' }}
|
{{ archiveInfo.status?.text ?? 'Нет в архиве' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="№ в архиве">
|
<NFormItem label="№ в архиве">
|
||||||
<NInput v-model:value="patientData.narhiv" />
|
<NInput v-model:value="archiveInfo.num" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="Дата поступления карты в архив">
|
<NFormItem label="Дата поступления карты в архив">
|
||||||
<NDatePicker v-model:value="patientData.datearhiv" format="dd.MM.yyyy" />
|
<NDatePicker v-model:value="archiveInfo.post_in" format="dd.MM.yyyy" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NForm>
|
</NForm>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NButton @click="onShowArchiveHistoryModal(null)">
|
<NButton @click="onShowArchiveHistoryModal(null)" :disabled="!patientData.can_be_issued">
|
||||||
Добавить
|
Добавить
|
||||||
</NButton>
|
</NButton>
|
||||||
<NDataTable :row-props="rowProps" min-height="420px" max-height="420px" :columns="columns" :data="patient?.journal" />
|
<NDataTable :row-props="rowProps" min-height="420px" max-height="420px" :columns="columns" :data="patient?.journal" />
|
||||||
@@ -116,14 +174,14 @@ watch(() => props.patientId, (newId) => {
|
|||||||
Закрыть без сохранения
|
Закрыть без сохранения
|
||||||
</NButton>
|
</NButton>
|
||||||
<NDivider vertical />
|
<NDivider vertical />
|
||||||
<NButton secondary type="primary">
|
<NButton secondary type="primary" @click="onSubmit">
|
||||||
Сохранить
|
Сохранить
|
||||||
</NButton>
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
<ArchiveHistoryMoveModal @close-without-save="selectedArchiveHistoryId = null" v-model:open="showArchiveHistoryModal" :archive-history-id="selectedArchiveHistoryId" />
|
<ArchiveHistoryMoveModal @history-updated="onUpdateHistory" @close-without-save="selectedArchiveHistoryId = null" :is-create-new="isCreateNewArchiveHistoryModal" v-model:open="showArchiveHistoryModal" :active-history-type="selectedArchiveHistoryType" :archive-history-id="selectedArchiveHistoryId" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {NButton, NDatePicker, NDivider, NFlex, NForm, NFormItem, NInput, NModal, NSpace, NSwitch, NSelect} from "naive-ui";
|
import {NButton, NDatePicker, NDivider, NFlex, NForm, NFormItem, NInput, NModal, NSpace, NSwitch, NSelect} from "naive-ui";
|
||||||
import {ref, watch} from "vue";
|
import {ref, watch} from "vue";
|
||||||
|
import {router} from "@inertiajs/vue3";
|
||||||
const open = defineModel('open')
|
const open = defineModel('open')
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
archiveHistoryId: {
|
archiveHistoryId: {
|
||||||
type: Number
|
type: Number
|
||||||
|
},
|
||||||
|
activeHistoryType: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
isCreateNew: {
|
||||||
|
type: Boolean
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['closeWithoutSave'])
|
const emits = defineEmits(['closeWithoutSave', 'historyUpdated'])
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const archiveHistory = ref({
|
const archiveHistory = ref({
|
||||||
historyable_id: props.archiveHistoryId,
|
historyable_id: props.archiveHistoryId,
|
||||||
|
historyable_type: props.activeHistoryType,
|
||||||
issue_at: null,
|
issue_at: null,
|
||||||
org_id: null,
|
org_id: null,
|
||||||
return_at: null,
|
return_at: null,
|
||||||
@@ -25,6 +33,7 @@ const orgs = ref([])
|
|||||||
const onResetData = () => {
|
const onResetData = () => {
|
||||||
archiveHistory.value = {
|
archiveHistory.value = {
|
||||||
historyable_id: props.archiveHistoryId,
|
historyable_id: props.archiveHistoryId,
|
||||||
|
historyable_type: props.activeHistoryType,
|
||||||
issue_at: null,
|
issue_at: null,
|
||||||
org_id: null,
|
org_id: null,
|
||||||
return_at: null,
|
return_at: null,
|
||||||
@@ -59,9 +68,19 @@ const loadArchiveHistoryData = async () => {
|
|||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
try {
|
try {
|
||||||
axios.post('/api/archive/histories/move', archiveHistory.value).then(res => {
|
const url = props.isCreateNew
|
||||||
|
? '/api/archive/histories/move'
|
||||||
|
: `/api/archive/histories/move/${archiveHistory.value.id}`
|
||||||
|
|
||||||
|
axios.post(url, archiveHistory.value).then(res => {
|
||||||
onCloseWithoutSave()
|
onCloseWithoutSave()
|
||||||
|
emits('historyUpdated', {
|
||||||
|
type: props.isCreateNew ? 'created' : 'updated',
|
||||||
|
data: res.data,
|
||||||
|
historyId: archiveHistory.value.id
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -71,7 +90,7 @@ const submit = () => {
|
|||||||
|
|
||||||
// Наблюдаем за изменением archiveHistoryId
|
// Наблюдаем за изменением archiveHistoryId
|
||||||
watch(() => props.archiveHistoryId, async (newId) => {
|
watch(() => props.archiveHistoryId, async (newId) => {
|
||||||
if (newId) {
|
if (!props.isCreateNew) {
|
||||||
await loadArchiveHistoryData()
|
await loadArchiveHistoryData()
|
||||||
} else {
|
} else {
|
||||||
onResetData()
|
onResetData()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { isLoading, handlePageChange, handlePageSizeChange, meta } = useMedicalHistoryFilter(props.filters)
|
const { isLoading, handlePageChange, handlePageSizeChange, meta, filtersRef } = useMedicalHistoryFilter(props.filters)
|
||||||
|
|
||||||
const archiveStatusColumn = (status) => {
|
const archiveStatusColumn = (status) => {
|
||||||
const tagType = status?.variant ?? 'error'
|
const tagType = status?.variant ?? 'error'
|
||||||
@@ -97,21 +97,41 @@ const rowProps = (row) => ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => {
|
||||||
page: meta.value.current_page || 1,
|
if (filtersRef.value.view_type === 'si') {
|
||||||
pageCount: meta.value.last_page || 1,
|
return {
|
||||||
pageSize: meta.value.per_page || 15,
|
page: meta.value.si.current_page || 1,
|
||||||
itemCount: meta.value.total || 0,
|
pageCount: meta.value.si.last_page || 1,
|
||||||
pageSizes: [15, 50, 100],
|
pageSize: meta.value.si.per_page || 15,
|
||||||
showSizePicker: true,
|
itemCount: meta.value.si.total || 0,
|
||||||
prefix({ itemCount }) {
|
pageSizes: [15, 50, 100],
|
||||||
return `Всего карт ${itemCount}.`
|
showSizePicker: true,
|
||||||
},
|
prefix({ itemCount }) {
|
||||||
onUpdatePage: (page) => {
|
return `Всего карт ${itemCount}.`
|
||||||
handlePageChange(page)
|
},
|
||||||
},
|
onUpdatePage: (page) => {
|
||||||
onUpdatePageSize: handlePageSizeChange
|
handlePageChange(page)
|
||||||
}))
|
},
|
||||||
|
onUpdatePageSize: handlePageSizeChange
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
page: meta.value.mis.current_page || 1,
|
||||||
|
pageCount: meta.value.mis.last_page || 1,
|
||||||
|
pageSize: meta.value.mis.per_page || 15,
|
||||||
|
itemCount: meta.value.mis.total || 0,
|
||||||
|
pageSizes: [15, 50, 100],
|
||||||
|
showSizePicker: true,
|
||||||
|
prefix({ itemCount }) {
|
||||||
|
return `Всего карт ${itemCount}.`
|
||||||
|
},
|
||||||
|
onUpdatePage: (page) => {
|
||||||
|
handlePageChange(page)
|
||||||
|
},
|
||||||
|
onUpdatePageSize: handlePageSizeChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import AppLayout from "../../Layouts/AppLayout.vue"
|
import AppLayout from "../../Layouts/AppLayout.vue"
|
||||||
import TableCards from './DataTable/Index.vue'
|
import TableCards from './DataTable/Index.vue'
|
||||||
import { NInput, NFlex, NDivider, NDatePicker, NSpace, NRadioGroup, NRadioButton, NH1 } from 'naive-ui'
|
import { NInput, NFlex, NDivider, NDatePicker, NSpace, NFormItem, NRadioButton, NH1, NTabs, NTabPane, NSelect } from 'naive-ui'
|
||||||
import {useMedicalHistory} from "../../Composables/useMedicalHistory.js";
|
import {useMedicalHistory} from "../../Composables/useMedicalHistory.js";
|
||||||
import {useDebounceFn} from "@vueuse/core";
|
import {useDebounceFn} from "@vueuse/core";
|
||||||
import {useMedicalHistoryFilter} from "../../Composables/useMedicalHistoryFilter.js";
|
import {useMedicalHistoryFilter} from "../../Composables/useMedicalHistoryFilter.js";
|
||||||
@@ -12,6 +12,10 @@ const props = defineProps({
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: []
|
||||||
},
|
},
|
||||||
|
statuses: {
|
||||||
|
type: Array,
|
||||||
|
default: []
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: []
|
||||||
@@ -20,35 +24,56 @@ const props = defineProps({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading, applyFilters, filtersRef, handleSearch, dateRange,
|
isLoading, applyFilters, filtersRef, handleSearch, dateRange,
|
||||||
handleDateRangeChange, handleViewTypeChange
|
handleDateRangeChange, handleViewTypeChange, handleStatusChange
|
||||||
} = useMedicalHistoryFilter(props.filters)
|
} = useMedicalHistoryFilter(props.filters)
|
||||||
|
|
||||||
const viewType = ref('archive')
|
const viewType = ref('archive')
|
||||||
|
|
||||||
|
const handleBeforeLeave = (tabName) => {
|
||||||
|
handleViewTypeChange(tabName)
|
||||||
|
return true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<template #header>
|
<template #header>
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NH1 class="!my-0 mb-5">
|
<!-- <NH1 class="!my-0 mb-5">-->
|
||||||
Архив
|
<!-- Архив-->
|
||||||
</NH1>
|
<!-- </NH1>-->
|
||||||
<NRadioGroup v-model:value="viewType" @update:value="val => handleViewTypeChange(val)">
|
<!-- <NRadioGroup v-model:value="viewType" @update:value="val => handleViewTypeChange(val)">-->
|
||||||
<NRadioButton value="archive">Архив</NRadioButton>
|
<!-- <NRadioButton value="archive">Архив</NRadioButton>-->
|
||||||
<NRadioButton value="mis">МИС</NRadioButton>
|
<!-- <NRadioButton value="mis">МИС</NRadioButton>-->
|
||||||
<NRadioButton value="softinfo">СофтИнфо</NRadioButton>
|
<!-- <NRadioButton value="softinfo">СофтИнфо</NRadioButton>-->
|
||||||
</NRadioGroup>
|
<!-- </NRadioGroup>-->
|
||||||
<NFlex class="pb-4" align="center" :wrap="false">
|
<NFlex class="pb-4" align="center" :wrap="false">
|
||||||
<NInput placeholder="Поиск по ФИО, № карты" @update:value="val => handleSearch(val)"/>
|
<NFormItem class="w-[720px]" label="Поиск" :show-feedback="false">
|
||||||
|
<NInput placeholder="Поиск по ФИО, № карты" @update:value="val => handleSearch(val)" size="large" />
|
||||||
|
</NFormItem>
|
||||||
<NDivider vertical />
|
<NDivider vertical />
|
||||||
<NDatePicker v-model:value="dateRange" @update:value="handleDateRangeChange" type="daterange" clearable format="dd.MM.yyyy" start-placeholder="Дата выписки с" end-placeholder="по" />
|
<NFormItem class="w-[340px]" label="Дата выписки" :show-feedback="false">
|
||||||
|
<NDatePicker v-model:value="dateRange" @update:value="handleDateRangeChange" type="daterange" clearable format="dd.MM.yyyy" start-placeholder="Дата выписки с" end-placeholder="по" size="large" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem class="w-[340px]" label="Статус карты" :show-feedback="false">
|
||||||
|
<NSelect :options="statuses" @update:value="val => handleStatusChange(val)" clearable size="large" />
|
||||||
|
</NFormItem>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
<TableCards :filters="filters" :data="cards.data" :meta="cards.meta" min-height="calc(100vh - 277px)" max-height="calc(100vh - 320px)" />
|
<NTabs :value="filtersRef.view_type" type="segment" animated @before-leave="handleBeforeLeave">
|
||||||
|
<NTabPane name="si" :tab="`СофтИнфо (${cards.si?.meta.total})`">
|
||||||
|
<TableCards :filters="filters" :data="cards.si?.data" :meta="cards.si?.meta" min-height="calc(100vh - 286px)" max-height="calc(100vh - 320px)" />
|
||||||
|
</NTabPane>
|
||||||
|
<NTabPane name="mis" :tab="`МИС (${cards.mis?.meta.total})`">
|
||||||
|
<TableCards :filters="filters" :data="cards.mis?.data" :meta="cards.mis?.meta" min-height="calc(100vh - 286px)" max-height="calc(100vh - 320px)" />
|
||||||
|
</NTabPane>
|
||||||
|
</NTabs>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
:deep(.n-tabs .n-tabs-nav) {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ Route::prefix('archive')->group(function () {
|
|||||||
Route::prefix('histories')->group(function () {
|
Route::prefix('histories')->group(function () {
|
||||||
Route::get('{id}', [\App\Http\Controllers\ArchiveHistoryController::class, 'show']);
|
Route::get('{id}', [\App\Http\Controllers\ArchiveHistoryController::class, 'show']);
|
||||||
Route::prefix('move')->group(function () {
|
Route::prefix('move')->group(function () {
|
||||||
Route::post('/', [\App\Http\Controllers\ArchiveHistoryController::class, 'move']);
|
Route::post('/', [\App\Http\Controllers\ArchiveHistoryController::class, 'moveStore']);
|
||||||
|
Route::post('{id}', [\App\Http\Controllers\ArchiveHistoryController::class, 'moveUpdate']);
|
||||||
|
});
|
||||||
|
Route::prefix('info')->group(function () {
|
||||||
|
Route::post('{patientId}', [\App\Http\Controllers\ArchiveHistoryController::class, 'infoUpdate']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user