* восстановление окна наблюдения
* добавил получение выбывших * фильтрация выбывших по результатам лечения * добавил подсказку при наведении на операции * добавил вывод причины наблюдения * добавил вкладки для выбывших * изменил связь и сохранение пациентов на контроле * добавил возможность редактирования причины контроля * полное изменение окна с нежелательными событиями * исправил просмотр причины контроля * работа над окном редактирования причины контроля в таблице * визуальное выделение умерших и проведенных операций * добавил выбор даты для роли врач * центрирование блоков статистики * разделение выполненных операций на срочность * поправил метод определения текущего дня для роли врач * функция блокировки при выборе другой даты для роли врач
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,7 @@ class StatisticController extends Controller
|
||||
->orderBy('reports.sent_at', 'desc')
|
||||
->first())->value ?? 0;
|
||||
|
||||
$percentLoadedBeds = $beds > 0 ? $occupiedBeds * 100 / $beds : 0;
|
||||
$percentLoadedBeds = $beds > 0 ? round($occupiedBeds * 100 / $beds) : 0;
|
||||
|
||||
$data[] = [
|
||||
'department' => $department->name_short,
|
||||
|
||||
@@ -27,7 +27,7 @@ class FormattedPatientResource extends JsonResource
|
||||
];
|
||||
}),
|
||||
'operations' => $this->whenLoaded('surgicalOperations', function () {
|
||||
return $this->surgicalOperations()->where('rf_StationarBranchID', $this->misStationarBranchId)
|
||||
return $this->operationOnBranch($this->misStationarBranchId, $this->startDate, $this->endDate)
|
||||
->get()
|
||||
->map(function (MisSurgicalOperation $operation) {
|
||||
return [
|
||||
@@ -38,6 +38,8 @@ class FormattedPatientResource extends JsonResource
|
||||
'fullname' => Str::ucwords(Str::lower("$this->FAMILY $this->Name $this->OT")),
|
||||
'age' => Carbon::parse($this->BD)->diff(Carbon::now())->format('%y'),
|
||||
'birth_date' => Carbon::parse($this->BD)->format('d.m.Y'),
|
||||
'outcome_type' => $this->when($this->outcome_type, $this->outcome_type),
|
||||
'comment' => $this->when($this->comment, $this->comment)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
21
app/Models/MedicalHistorySnapshot.php
Normal file
21
app/Models/MedicalHistorySnapshot.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MedicalHistorySnapshot extends Model
|
||||
{
|
||||
protected $primaryKey = 'medical_history_snapshot_id';
|
||||
|
||||
protected $fillable = [
|
||||
'rf_report_id',
|
||||
'rf_medicalhistory_id',
|
||||
'patient_type',
|
||||
];
|
||||
|
||||
public function report()
|
||||
{
|
||||
return $this->belongsTo(Report::class, 'rf_report_id');
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class MisMedicalHistory extends Model
|
||||
|
||||
public function observationPatient()
|
||||
{
|
||||
return $this->belongsTo(ObservationPatient::class, 'MedicalHistoryID', 'rf_medicalhistory_id');
|
||||
return $this->hasMany(ObservationPatient::class, 'rf_medicalhistory_id', 'MedicalHistoryID');
|
||||
}
|
||||
|
||||
public function surgicalOperations()
|
||||
@@ -34,6 +34,12 @@ class MisMedicalHistory extends Model
|
||||
return $this->hasMany(MisSurgicalOperation::class, 'rf_MedicalHistoryID', 'MedicalHistoryID');
|
||||
}
|
||||
|
||||
public function scopeOperationOnBranch($query, $branchId, $startDate, $endDate)
|
||||
{
|
||||
return $this->surgicalOperations()->where('rf_StationarBranchID', $branchId)
|
||||
->whereBetween('Date', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
public function scopeCurrentlyHospitalized($query)
|
||||
{
|
||||
return $query->whereDate('DateExtract', '1900-01-01')
|
||||
@@ -43,25 +49,25 @@ class MisMedicalHistory extends Model
|
||||
/*
|
||||
* Истории со срочностью - Плановая
|
||||
*/
|
||||
public function scopePlan()
|
||||
public function scopePlan($query)
|
||||
{
|
||||
return $this->where('rf_EmerSignID', 1);
|
||||
return $query->where('rf_EmerSignID', 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Истории со срочностью - Экстренная
|
||||
*/
|
||||
public function scopeEmergency()
|
||||
public function scopeEmergency($query)
|
||||
{
|
||||
return $this->where('rf_EmerSignID', 2);
|
||||
return $query->where('rf_EmerSignID', 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Истории с результатом - Умер
|
||||
*/
|
||||
public function scopeDeceased()
|
||||
public function scopeDeceased($query)
|
||||
{
|
||||
return $this->where('rf_kl_VisitResultID', 5);
|
||||
return $query->where('rf_kl_VisitResultID', 5);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -25,7 +25,7 @@ class MisMigrationPatient extends Model
|
||||
return $this->hasOne(MisMKB::class, 'MKBID', 'rf_MKBID');
|
||||
}
|
||||
|
||||
public function scopeCurrentlyInTreatment($query, $branchId = null)
|
||||
public function scopeCurrentlyInTreatment($query, $branchId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
$query->where('rf_kl_VisitResultID', 0)
|
||||
->where('rf_MedicalHistoryID', '<>', 0);
|
||||
@@ -34,6 +34,10 @@ class MisMigrationPatient extends Model
|
||||
$query->where('rf_StationarBranchID', $branchId);
|
||||
}
|
||||
|
||||
if ($startDate && $endDate) {
|
||||
$query->whereBetween('DateIngoing', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -48,6 +52,95 @@ class MisMigrationPatient extends Model
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выбывшие пациенты (все исходы)
|
||||
*/
|
||||
public function scopeOutcomePatients($query, $branchId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
$query->where('rf_kl_VisitResultID', '<>', 0) // не активное лечение
|
||||
->whereDate('DateOut', '<>', '1900-01-01') // есть дата выбытия
|
||||
->where('rf_MedicalHistoryID', '<>', 0);
|
||||
|
||||
if ($branchId) {
|
||||
$query->where('rf_StationarBranchID', $branchId);
|
||||
}
|
||||
|
||||
if ($startDate && $endDate) {
|
||||
$query->whereBetween('DateOut', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выписанные пациенты
|
||||
*/
|
||||
public function scopeDischarged($query, $branchId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
// ID выписки
|
||||
$dischargeCodes = [1, 7, 8, 9, 10, 11, 48, 49, 124];
|
||||
|
||||
$query->whereIn('rf_kl_VisitResultID', $dischargeCodes)
|
||||
->whereDate('DateOut', '<>', '1900-01-01')
|
||||
->where('rf_MedicalHistoryID', '<>', 0);
|
||||
|
||||
if ($branchId) {
|
||||
$query->where('rf_StationarBranchID', $branchId);
|
||||
}
|
||||
|
||||
if ($startDate && $endDate) {
|
||||
$query->whereBetween('DateOut', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Перевод в другое отделение
|
||||
*/
|
||||
public function scopeTransferred($query, $branchId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
// ID перевода
|
||||
$transferCodes = [2, 3, 4, 12, 13, 14];
|
||||
|
||||
$query->whereIn('rf_kl_VisitResultID', $transferCodes)
|
||||
->whereDate('DateOut', '<>', '1900-01-01')
|
||||
->where('rf_MedicalHistoryID', '<>', 0);
|
||||
|
||||
if ($branchId) {
|
||||
$query->where('rf_StationarBranchID', $branchId);
|
||||
}
|
||||
|
||||
if ($startDate && $endDate) {
|
||||
$query->whereBetween('DateOut', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Умершие пациенты
|
||||
*/
|
||||
public function scopeDeceasedOutcome($query, $branchId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
// ID умершего
|
||||
$deceasedCodes = [5, 6, 15, 16];
|
||||
|
||||
$query->whereIn('rf_kl_VisitResultID', $deceasedCodes)
|
||||
->whereDate('DateOut', '<>', '1900-01-01')
|
||||
->where('rf_MedicalHistoryID', '<>', 0);
|
||||
|
||||
if ($branchId) {
|
||||
$query->where('rf_StationarBranchID', $branchId);
|
||||
}
|
||||
|
||||
if ($startDate && $endDate) {
|
||||
$query->whereBetween('DateOut', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function scopeExtractedToday($query, $branchId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d');
|
||||
|
||||
@@ -25,4 +25,9 @@ class Report extends Model
|
||||
{
|
||||
return $this->hasMany(ObservationPatient::class, 'rf_report_id', 'report_id');
|
||||
}
|
||||
|
||||
public function unwantedEvents()
|
||||
{
|
||||
return $this->hasMany(UnwantedEvent::class, 'rf_report_id', 'report_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ class UnwantedEvent extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'rf_report_id',
|
||||
'comment'
|
||||
'comment',
|
||||
'title',
|
||||
'is_visible',
|
||||
];
|
||||
|
||||
public function report()
|
||||
{
|
||||
return $this->belongsTo(Report::class, 'rf_report_id');
|
||||
}
|
||||
}
|
||||
|
||||
70
app/Services/DateRangeService.php
Normal file
70
app/Services/DateRangeService.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class DateRangeService
|
||||
{
|
||||
public function getDateRangeForUser($user, $startAt = null, $endAt = null): array
|
||||
{
|
||||
if ($startAt && $endAt) {
|
||||
return $this->getCustomDateRange($startAt, $endAt, $user);
|
||||
}
|
||||
|
||||
return $this->getDefaultDateRange($user);
|
||||
}
|
||||
|
||||
private function getCustomDateRange($startAt, $endAt, $user): array
|
||||
{
|
||||
$startDate = $this->parseDate($startAt);
|
||||
$endDate = $this->parseDate($endAt);
|
||||
|
||||
if ($startDate->isSameDay($endDate)) {
|
||||
$startDate = $startDate->subDay()->setTime(6, 0);
|
||||
$endDate = $endDate->setTime(6, 0);
|
||||
} else {
|
||||
$startDate = $startDate->setTime(6, 0);
|
||||
$endDate = $endDate->setTime(6, 0);
|
||||
}
|
||||
|
||||
return [
|
||||
$startDate->format('Y-m-d H:i:s'),
|
||||
$endDate->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
|
||||
private function getDefaultDateRange($user): array
|
||||
{
|
||||
if ($user->isHeadOfDepartment() || $user->isAdmin()) {
|
||||
$startDate = Carbon::now('Asia/Yakutsk')
|
||||
->firstOfMonth()
|
||||
->setTime(6, 0);
|
||||
|
||||
$endDate = Carbon::now('Asia/Yakutsk')
|
||||
->setTime(6, 0);
|
||||
} else {
|
||||
$startDate = Carbon::now('Asia/Yakutsk')
|
||||
->subDay()
|
||||
->setTime(6, 0);
|
||||
|
||||
$endDate = Carbon::now('Asia/Yakutsk')
|
||||
->setTime(6, 0);
|
||||
}
|
||||
|
||||
return [
|
||||
$startDate->format('Y-m-d H:i:s'),
|
||||
$endDate->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
|
||||
private function parseDate($dateInput): Carbon
|
||||
{
|
||||
if (is_numeric($dateInput)) {
|
||||
return Carbon::createFromTimestampMs($dateInput)
|
||||
->setTimezone('Asia/Yakutsk');
|
||||
}
|
||||
|
||||
return Carbon::parse($dateInput, 'Asia/Yakutsk');
|
||||
}
|
||||
}
|
||||
449
app/Services/PatientService.php
Normal file
449
app/Services/PatientService.php
Normal file
@@ -0,0 +1,449 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\MisMigrationPatient;
|
||||
use App\Models\MisMedicalHistory;
|
||||
use App\Models\MisSurgicalOperation;
|
||||
use App\Models\ObservationPatient;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class PatientService
|
||||
{
|
||||
public function getPatientsByType(
|
||||
string $type,
|
||||
?bool $isHeadOrAdmin,
|
||||
?int $branchId,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null,
|
||||
bool $countOnly = false,
|
||||
bool $onlyIds = false
|
||||
): Collection|int|array {
|
||||
// Обрабатываем outcome- типы
|
||||
if (str_starts_with($type, 'outcome-')) {
|
||||
$outcomeType = str_replace('outcome-', '', $type);
|
||||
return $this->getOutcomePatients($branchId, $startDate, $endDate, $outcomeType, $countOnly, $onlyIds);
|
||||
}
|
||||
|
||||
// Обрабатываем обычные типы
|
||||
$method = 'get' . ucfirst($type) . 'Patients';
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->$method($isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Unknown patient type: {$type}");
|
||||
}
|
||||
|
||||
public function getPlanPatients(
|
||||
?bool $isHeadOrAdmin,
|
||||
?int $branchId,
|
||||
?string $startDate,
|
||||
?string $endDate,
|
||||
bool $countOnly,
|
||||
bool $onlyIds
|
||||
): Collection|int|array {
|
||||
return $this->getAdmissionPatients('plan', $isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds);
|
||||
}
|
||||
|
||||
public function getEmergencyPatients(
|
||||
?bool $isHeadOrAdmin,
|
||||
?int $branchId,
|
||||
?string $startDate,
|
||||
?string $endDate,
|
||||
bool $countOnly,
|
||||
bool $onlyIds
|
||||
): Collection|int|array {
|
||||
return $this->getAdmissionPatients('emergency', $isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds);
|
||||
}
|
||||
|
||||
private function getAdmissionPatients(
|
||||
string $admissionType,
|
||||
?bool $isHeadOrAdmin,
|
||||
?int $branchId,
|
||||
?string $startDate,
|
||||
?string $endDate,
|
||||
bool $countOnly,
|
||||
bool $onlyIds
|
||||
): Collection|int|array {
|
||||
$query = $this->getBasePatientsQuery($isHeadOrAdmin, $branchId, $startDate, $endDate);
|
||||
|
||||
if ($admissionType === 'plan') {
|
||||
$query->plan();
|
||||
} else {
|
||||
$query->emergency();
|
||||
}
|
||||
|
||||
return $this->executeQuery($query, $countOnly, $onlyIds);
|
||||
}
|
||||
|
||||
private function getBasePatientsQuery(
|
||||
?bool $isHeadOrAdmin,
|
||||
?int $branchId,
|
||||
?string $startDate,
|
||||
?string $endDate
|
||||
) {
|
||||
$migrationPatient = new MisMigrationPatient();
|
||||
|
||||
if ($isHeadOrAdmin) {
|
||||
$medicalHistoryIds = $migrationPatient->newQuery()
|
||||
->whereInDepartment($branchId)
|
||||
->whereBetween('DateIngoing', [$startDate, $endDate])
|
||||
->pluck('rf_MedicalHistoryID')
|
||||
->toArray();
|
||||
} else {
|
||||
$medicalHistoryIds = $migrationPatient->newQuery()
|
||||
->currentlyInTreatment($branchId)
|
||||
->whereBetween('DateIngoing', [$startDate, $endDate])
|
||||
->pluck('rf_MedicalHistoryID')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
if (empty($medicalHistoryIds)) {
|
||||
return MisMedicalHistory::whereRaw('1=0'); // Пустой запрос
|
||||
}
|
||||
|
||||
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
|
||||
->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) {
|
||||
$query->whereBetween('Date', [$startDate, $endDate]);
|
||||
}])
|
||||
->orderBy('DateRecipient', 'DESC');
|
||||
}
|
||||
|
||||
private function executeQuery($query, bool $countOnly, bool $onlyIds): Collection|int|array
|
||||
{
|
||||
if ($onlyIds) {
|
||||
return $query->pluck('MedicalHistoryID')->toArray();
|
||||
}
|
||||
|
||||
if ($countOnly) {
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
public function getObservationPatients(int $departmentId): Collection
|
||||
{
|
||||
return ObservationPatient::where('rf_department_id', $departmentId)
|
||||
->with(['medicalHistory'])
|
||||
->get()
|
||||
->map(function ($observation) {
|
||||
$patient = $observation->medicalHistory;
|
||||
$patient->observation_comment = $observation->comment;
|
||||
return $patient;
|
||||
});
|
||||
}
|
||||
|
||||
public function getOutcomePatients(int $branchId, string $startDate, string $endDate, string $outcomeType, $countOnly = false, $onlyIds = false): Collection|int
|
||||
{
|
||||
return match($outcomeType) {
|
||||
'discharged' => $this->getDischargedOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds),
|
||||
'transferred' => $this->getTransferredOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds),
|
||||
'deceased' => $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds),
|
||||
default => throw new \InvalidArgumentException("Неизвестный тип исхода: {$outcomeType}")
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить всех выбывших пациентов
|
||||
*/
|
||||
public function getAllOutcomePatients(
|
||||
int $branchId,
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
bool $countOnly = false
|
||||
): Collection|int {
|
||||
$migrationPatient = new MisMigrationPatient();
|
||||
|
||||
// Получаем миграции выбывших пациентов за период
|
||||
$migrations = $migrationPatient->newQuery()
|
||||
->outcomePatients($branchId, $startDate, $endDate)
|
||||
->select('rf_MedicalHistoryID', 'rf_kl_VisitResultID', 'DateOut')
|
||||
->get()
|
||||
->groupBy('rf_MedicalHistoryID');
|
||||
|
||||
if ($migrations->isEmpty()) {
|
||||
return $countOnly ? 0 : collect();
|
||||
}
|
||||
|
||||
$medicalHistoryIds = $migrations->keys()->toArray();
|
||||
|
||||
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
|
||||
->with(['surgicalOperations'])
|
||||
->orderBy('DateRecipient', 'DESC');
|
||||
|
||||
if ($countOnly) {
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
$patients = $query->get();
|
||||
|
||||
return $this->addOutcomeInfoToPatients($patients, $migrations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить миграции выбывших пациентов
|
||||
*/
|
||||
private function getOutcomeMigrations(int $branchId, string $startDate, string $endDate): Collection
|
||||
{
|
||||
$migrationPatient = new MisMigrationPatient();
|
||||
|
||||
return $migrationPatient->newQuery()
|
||||
->outcomePatients($branchId, $startDate, $endDate)
|
||||
->select('rf_MedicalHistoryID', 'rf_kl_VisitResultID', 'DateOut')
|
||||
->get()
|
||||
->groupBy('rf_MedicalHistoryID');
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить информацию о выбытии к пациентам
|
||||
*/
|
||||
private function addOutcomeInfoToPatients(Collection $patients, Collection $migrations): Collection
|
||||
{
|
||||
return $patients->map(function ($patient) use ($migrations) {
|
||||
$patientMigrations = $migrations->get($patient->MedicalHistoryID, collect());
|
||||
|
||||
if ($patientMigrations->isNotEmpty()) {
|
||||
$latestMigration = $patientMigrations->sortByDesc('DateOut')->first();
|
||||
$patient->outcome_type = $this->getOutcomeTypeName($latestMigration->rf_kl_VisitResultID);
|
||||
$patient->outcome_date = $latestMigration->DateOut;
|
||||
$patient->visit_result_id = $latestMigration->rf_kl_VisitResultID;
|
||||
}
|
||||
|
||||
return $patient;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить понятное название типа выбытия
|
||||
*/
|
||||
private function getOutcomeTypeName(int $visitResultId): string
|
||||
{
|
||||
return match($visitResultId) {
|
||||
1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка',
|
||||
2, 3, 4, 12, 13, 14 => 'Перевод',
|
||||
5, 6, 15, 16 => 'Умер',
|
||||
default => 'Другое (' . $visitResultId . ')'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить выписанных пациентов
|
||||
*/
|
||||
public function getDischargedOutcomePatients(
|
||||
int $branchId,
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
bool $countOnly = false,
|
||||
bool $onlyIds = false
|
||||
): Collection|array|int {
|
||||
return $this->getSpecificOutcomePatients('discharged', $branchId, $startDate, $endDate, $onlyIds, $countOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить переведенных пациентов
|
||||
*/
|
||||
public function getTransferredOutcomePatients(
|
||||
int $branchId,
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
bool $countOnly = false,
|
||||
bool $onlyIds = false
|
||||
): Collection|array|int {
|
||||
return $this->getSpecificOutcomePatients('transferred', $branchId, $startDate, $endDate, $onlyIds, $countOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить умерших пациентов
|
||||
*/
|
||||
public function getDeceasedOutcomePatients(
|
||||
int $branchId,
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
bool $countOnly = false,
|
||||
bool $onlyIds = false
|
||||
): Collection|int|array {
|
||||
return $this->getSpecificOutcomePatients('deceased', $branchId, $startDate, $endDate, $onlyIds, $countOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Общий метод для получения пациентов с определенным типом выбытия
|
||||
*/
|
||||
private function getSpecificOutcomePatients(
|
||||
string $type,
|
||||
int $branchId,
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
bool $onlyIds = false,
|
||||
bool $countOnly = false
|
||||
): Collection|int|array {
|
||||
$migrationPatient = new MisMigrationPatient();
|
||||
|
||||
switch ($type) {
|
||||
case 'discharged':
|
||||
$query = $migrationPatient->newQuery()->discharged($branchId, $startDate, $endDate);
|
||||
break;
|
||||
case 'transferred':
|
||||
$query = $migrationPatient->newQuery()->transferred($branchId, $startDate, $endDate);
|
||||
break;
|
||||
case 'deceased':
|
||||
$query = $migrationPatient->newQuery()->deceasedOutcome($branchId, $startDate, $endDate);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Неизвестный тип выбытия: {$type}");
|
||||
}
|
||||
|
||||
$medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->unique()->toArray();
|
||||
|
||||
if (empty($medicalHistoryIds)) {
|
||||
if ($countOnly) return 0;
|
||||
if ($onlyIds) return [];
|
||||
return collect();
|
||||
}
|
||||
|
||||
if ($onlyIds) {
|
||||
return $medicalHistoryIds;
|
||||
}
|
||||
|
||||
$patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
|
||||
->with(['surgicalOperations'])
|
||||
->orderBy('DateRecipient', 'DESC');
|
||||
|
||||
if ($countOnly) {
|
||||
return $patients->count();
|
||||
}
|
||||
|
||||
return $patients->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить пациентов, находящихся на лечении
|
||||
*/
|
||||
public function getCurrentPatients(
|
||||
int $branchId,
|
||||
bool $countOnly = false,
|
||||
bool $onlyIds = false
|
||||
): Collection|int|array {
|
||||
$migrationPatient = new MisMigrationPatient();
|
||||
|
||||
$medicalHistoryIds = $migrationPatient->newQuery()
|
||||
->currentlyInTreatment($branchId)
|
||||
->pluck('rf_MedicalHistoryID')
|
||||
->unique()
|
||||
->toArray();
|
||||
|
||||
if (empty($medicalHistoryIds)) {
|
||||
if ($countOnly) return 0;
|
||||
if ($onlyIds) return [];
|
||||
return collect();
|
||||
}
|
||||
|
||||
if ($onlyIds) {
|
||||
return $medicalHistoryIds;
|
||||
}
|
||||
|
||||
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
|
||||
->currentlyHospitalized()
|
||||
->with(['surgicalOperations'])
|
||||
->orderBy('DateRecipient', 'DESC');
|
||||
|
||||
if ($countOnly) {
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить пациентов с операциями
|
||||
*/
|
||||
public function getSurgicalPatients(
|
||||
string $status,
|
||||
bool $isHeadOrAdmin,
|
||||
int $branchId,
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
bool $countOnly = false
|
||||
): Collection|int {
|
||||
$query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId)
|
||||
->whereBetween('Date', [$startDate, $endDate])
|
||||
->orderBy('Date', 'DESC');
|
||||
|
||||
if ($status === 'plan') {
|
||||
$query->where('rf_TypeSurgOperationInTimeID', 6);
|
||||
} else {
|
||||
$query->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]);
|
||||
}
|
||||
|
||||
if ($countOnly) {
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить пациентов (плановых или экстренных), которые были в отделении в течение периода
|
||||
*/
|
||||
public function getPatientsInDepartmentDuringPeriod(
|
||||
?string $patientType, // 'plan', 'emergency', null (все)
|
||||
?bool $isHeadOrAdmin,
|
||||
?int $branchId,
|
||||
?string $startDate,
|
||||
?string $endDate,
|
||||
bool $countOnly = false,
|
||||
bool $onlyIds = false,
|
||||
bool $today = false
|
||||
): Collection|int|array {
|
||||
// Используем скоуп inDepartment из модели MisMedicalHistory
|
||||
$query = MisMedicalHistory::query()
|
||||
->whereHas('migrations', function ($q) use ($branchId, $startDate, $endDate) {
|
||||
$q->where('rf_StationarBranchID', $branchId)
|
||||
->where('rf_MedicalHistoryID', '<>', 0)
|
||||
->where(function ($subQ) use ($startDate, $endDate) {
|
||||
// Пациент находился в отделении в течение периода
|
||||
// 1. Поступил в течение периода
|
||||
$subQ->whereBetween('DateIngoing', [$startDate, $endDate])
|
||||
// 2. Или выбыл в течение периода
|
||||
->orWhereBetween('DateOut', [$startDate, $endDate])
|
||||
// 3. Или находился в отделении в течение всего периода
|
||||
->orWhere(function ($innerQ) use ($startDate, $endDate) {
|
||||
$innerQ->where('DateIngoing', '<=', $startDate)
|
||||
->where(function ($deepQ) use ($endDate) {
|
||||
$deepQ->where('DateOut', '>=', $endDate)
|
||||
->orWhereNull('DateOut')
|
||||
->orWhere('DateOut', '1900-01-01');
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) {
|
||||
$query->whereBetween('Date', [$startDate, $endDate]);
|
||||
}])
|
||||
->orderBy('DateRecipient', 'DESC');
|
||||
|
||||
// Фильтруем по типу (план/экстренность)
|
||||
if ($patientType === 'plan') {
|
||||
$query->plan();
|
||||
} elseif ($patientType === 'emergency') {
|
||||
$query->emergency();
|
||||
}
|
||||
|
||||
// Для врача добавляем условие "все еще в отделении"
|
||||
if (!$isHeadOrAdmin && !$today) {
|
||||
$query->currentlyHospitalized();
|
||||
}
|
||||
|
||||
if ($onlyIds) {
|
||||
return $query->select('MedicalHistoryID')
|
||||
->pluck('MedicalHistoryID')->values();
|
||||
}
|
||||
|
||||
if ($countOnly) {
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
}
|
||||
125
app/Services/ReportService.php
Normal file
125
app/Services/ReportService.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Report;
|
||||
use App\Models\MetrikaResult;
|
||||
use App\Models\UnwantedEvent;
|
||||
use App\Models\ObservationPatient;
|
||||
use App\Models\MedicalHistorySnapshot;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ReportService
|
||||
{
|
||||
private PatientService $patientService;
|
||||
|
||||
public function __construct(PatientService $patientService)
|
||||
{
|
||||
$this->patientService = $patientService;
|
||||
}
|
||||
|
||||
public function createReport(array $data): Report
|
||||
{
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$report = Report::create([
|
||||
'rf_department_id' => $data['departmentId'],
|
||||
'rf_user_id' => Auth::id(),
|
||||
'sent_at' => now()
|
||||
]);
|
||||
|
||||
$this->saveMetrics($report, $data['metrics'] ?? []);
|
||||
$this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []);
|
||||
$this->saveObservationPatients($report, $data['observationPatients'] ?? []);
|
||||
$this->savePatientSnapshots($report, $data);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return $report;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function saveMetrics(Report $report, array $metrics): void
|
||||
{
|
||||
foreach ($metrics as $key => $value) {
|
||||
MetrikaResult::create([
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_metrika_item_id' => (int) str_replace('metrika_item_', '', $key),
|
||||
'value' => $value
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function saveUnwantedEvents(Report $report, array $unwantedEvents): void
|
||||
{
|
||||
foreach ($unwantedEvents as $event) {
|
||||
if (isset($event['unwanted_event_id'])) {
|
||||
UnwantedEvent::updateOrCreate(
|
||||
['unwanted_event_id' => $event['unwanted_event_id']],
|
||||
$this->formatUnwantedEventData($report, $event)
|
||||
);
|
||||
} else {
|
||||
UnwantedEvent::create($this->formatUnwantedEventData($report, $event));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function formatUnwantedEventData(Report $report, array $event): array
|
||||
{
|
||||
return [
|
||||
'rf_report_id' => $report->report_id,
|
||||
'comment' => $event['comment'] ?? '',
|
||||
'title' => $event['title'] ?? '',
|
||||
'is_visible' => $event['is_visible'] ?? true,
|
||||
];
|
||||
}
|
||||
|
||||
private function saveObservationPatients(Report $report, array $observationPatients): void
|
||||
{
|
||||
foreach ($observationPatients as $patient) {
|
||||
ObservationPatient::create([
|
||||
'rf_department_id' => $report->rf_department_id,
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_medicalhistory_id' => $patient['id'],
|
||||
'comment' => $patient['comment'] ?? null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function savePatientSnapshots(Report $report, array $data): void
|
||||
{
|
||||
$snapshotTypes = [
|
||||
'plan' => $this->patientService->getPlanPatients(
|
||||
false,
|
||||
$data['branchId'],
|
||||
$data['startDate'],
|
||||
$data['endDate'],
|
||||
false,
|
||||
true
|
||||
),
|
||||
'emergency' => $this->patientService->getEmergencyPatients(
|
||||
false,
|
||||
$data['branchId'],
|
||||
$data['startDate'],
|
||||
$data['endDate'],
|
||||
false,
|
||||
true
|
||||
)
|
||||
];
|
||||
|
||||
foreach ($snapshotTypes as $type => $patientIds) {
|
||||
foreach ($patientIds as $patientId) {
|
||||
MedicalHistorySnapshot::create([
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_medicalhistory_id' => $patientId,
|
||||
'patient_type' => $type
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('unwanted_events', function (Blueprint $table) {
|
||||
$table->string('title')->nullable();
|
||||
$table->string('is_visible')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('unwanted_events', function (Blueprint $table) {
|
||||
$table->dropColumn('title');
|
||||
$table->dropColumn('is_visible');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('medical_history_snapshots', function (Blueprint $table) {
|
||||
$table->id('medical_history_snapshot_id');
|
||||
$table->foreignIdFor(\App\Models\Report::class, 'rf_report_id');
|
||||
$table->foreignIdFor(\App\Models\MisMedicalHistory::class, 'rf_medicalhistory_id');
|
||||
$table->string('patient_type');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('medical_history_snapshots');
|
||||
}
|
||||
};
|
||||
@@ -2,34 +2,158 @@
|
||||
import {NDatePicker} from 'naive-ui'
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useReportStore} from "../Stores/report.js";
|
||||
|
||||
const themeOverride = {
|
||||
peers: {
|
||||
Input: {
|
||||
border: null,
|
||||
color: null,
|
||||
colorFocus: null,
|
||||
borderHover: null,
|
||||
borderFocus: null,
|
||||
boxShadowFocus: null,
|
||||
paddingMedium: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
import {useAuthStore} from "../Stores/auth.js";
|
||||
import {computed, ref, onMounted, onUnmounted} from "vue";
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const { timestampCurrentRange } = storeToRefs(reportStore)
|
||||
const authStore = useAuthStore()
|
||||
const {timestampCurrentRange} = storeToRefs(reportStore)
|
||||
|
||||
// Текущее время для обновления
|
||||
const currentTime = ref(Date.now())
|
||||
|
||||
// Обновляем время каждую секунду
|
||||
let intervalId = null
|
||||
onMounted(() => {
|
||||
if (authStore.isDoctor) {
|
||||
intervalId = setInterval(() => {
|
||||
currentTime.value = Date.now()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
})
|
||||
|
||||
// Проверяем, является ли дата сегодняшней
|
||||
const isToday = (timestamp) => {
|
||||
const date = new Date(timestamp)
|
||||
const today = new Date()
|
||||
|
||||
return date.getDate() === today.getDate() &&
|
||||
date.getMonth() === today.getMonth() &&
|
||||
date.getFullYear() === today.getFullYear()
|
||||
}
|
||||
|
||||
// Функция для форматирования с заглавной буквой
|
||||
const formatDateWithCapital = (timestamp) => {
|
||||
const date = new Date(timestamp)
|
||||
// Форматируем дату на русском
|
||||
const formatted = date.toLocaleDateString('ru-RU', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
// Делаем первую букву заглавной
|
||||
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
|
||||
}
|
||||
|
||||
const themeOverride = computed(() => {
|
||||
if (authStore.isDoctor) {
|
||||
return {
|
||||
peers: {
|
||||
Input: {
|
||||
border: '',
|
||||
borderHover: '',
|
||||
borderFocus: '',
|
||||
boxShadowFocus: '',
|
||||
color: '',
|
||||
colorFocus: '',
|
||||
fontSizeMedium: '22px',
|
||||
fontWeight: '500'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Используем кастомную функцию форматирования
|
||||
const formatComputed = computed(() => {
|
||||
const baseFormat = "dd.MM.yyyy"
|
||||
|
||||
if (authStore.isDoctor) {
|
||||
// Возвращаем функцию форматирования вместо строки
|
||||
return formatDateWithCapital(modelComputed.value)
|
||||
}
|
||||
|
||||
return baseFormat
|
||||
})
|
||||
|
||||
const typeComputed = computed(() => authStore.isDoctor ? 'date' : 'daterange')
|
||||
|
||||
const modelComputed = computed({
|
||||
get: () => {
|
||||
const value = reportStore.timestampCurrentRange
|
||||
|
||||
if (authStore.isDoctor) {
|
||||
if (Array.isArray(value)) {
|
||||
const doctorTimestamp = value[1]
|
||||
|
||||
// Если выбрана сегодняшняя дата - показываем текущее время
|
||||
if (isToday(doctorTimestamp)) {
|
||||
return currentTime.value
|
||||
}
|
||||
// Иначе показываем выбранную дату (без обновления)
|
||||
return doctorTimestamp
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
} else {
|
||||
return [value, value]
|
||||
}
|
||||
},
|
||||
set: (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
reportStore.timestampCurrentRange = value
|
||||
} else {
|
||||
reportStore.timestampCurrentRange = [value, value]
|
||||
}
|
||||
|
||||
reportStore.getDataOnReportDate(reportStore.timestampCurrentRange)
|
||||
}
|
||||
})
|
||||
|
||||
const classComputed = computed(() => {
|
||||
const baseClasses = []
|
||||
|
||||
if (authStore.isDoctor) {
|
||||
baseClasses.push('w-[400px]')
|
||||
baseClasses.push('text-end')
|
||||
} else {
|
||||
baseClasses.push('max-w-[280px]')
|
||||
}
|
||||
|
||||
return baseClasses
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDatePicker :theme-overrides="themeOverride"
|
||||
v-model:value="reportStore.timestampCurrentRange"
|
||||
format="dd.MM.YYYY"
|
||||
type="daterange"
|
||||
@update-value="value => reportStore.getDataOnReportDate(value)"
|
||||
<NDatePicker
|
||||
:theme-overrides="themeOverride"
|
||||
v-model:value="modelComputed"
|
||||
:class="classComputed"
|
||||
:format="formatComputed"
|
||||
:type="typeComputed"
|
||||
placement="top-end"
|
||||
input-readonly
|
||||
bind-calendar-months
|
||||
update-value-on-close
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
:deep(.n-input__suffix) {
|
||||
margin-left: 12px;
|
||||
}
|
||||
:deep(.n-input),
|
||||
:deep(.n-input__input-el) {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
56
resources/js/Pages/Report/Components/MoveModalComment.vue
Normal file
56
resources/js/Pages/Report/Components/MoveModalComment.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script setup>
|
||||
import {NModal, NForm, NFormItem, NInput, NButton} from 'naive-ui'
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {ref, watch} from "vue";
|
||||
const show = defineModel('show')
|
||||
const props = defineProps({
|
||||
patientId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
const reportStore = useReportStore()
|
||||
const droppedPatient = ref(null)
|
||||
|
||||
const rules = {
|
||||
comment: {
|
||||
required: true,
|
||||
trigger: ['blur'],
|
||||
message: 'Введите описание причины'
|
||||
}
|
||||
}
|
||||
watch(() => props.patientId, (newPatientId) => {
|
||||
if (newPatientId) {
|
||||
const patientIndex = reportStore.patientsData.observation.findIndex(itm => itm.id === newPatientId)
|
||||
const patient = reportStore.patientsData.observation[patientIndex]
|
||||
droppedPatient.value = patient
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="show"
|
||||
title="Постановка на контроль"
|
||||
:mask-closable="false"
|
||||
:closable="false"
|
||||
class="max-w-lg"
|
||||
preset="card"
|
||||
>
|
||||
<NForm :model="droppedPatient" :rules="rules">
|
||||
<NFormItem label="Опишите причину" path="comment">
|
||||
<NInput type="textarea" :rows="6" :resizable="false" v-model:value="droppedPatient.comment" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #action>
|
||||
<div class="flex justify-end">
|
||||
<NButton type="primary" secondary @click="show = false">
|
||||
Сохранить
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@ import ReportFormInput from "./ReportFormInput.vue";
|
||||
import ReportSection from "./ReportSection.vue";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {useAuthStore} from "../../../Stores/auth.js";
|
||||
import {computed} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
@@ -21,17 +22,19 @@ const onSubmit = () => {
|
||||
departmentId: authStore.userDepartment.department_id
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex vertical class="max-w-6xl mx-auto mt-6 mb-4 w-full">
|
||||
<ReportHeader :mode="mode" />
|
||||
|
||||
<ReportFormInput v-if="mode === 'fillable'" />
|
||||
<ReportFormInput />
|
||||
|
||||
<ReportSection label="Планово" />
|
||||
|
||||
<NButton secondary size="large" @click="onSubmit">
|
||||
<NButton v-if="reportStore.reportInfo?.report.isActiveSendButton" secondary size="large" @click="onSubmit">
|
||||
Сохранить отчет
|
||||
</NButton>
|
||||
</NFlex>
|
||||
|
||||
@@ -1,30 +1,69 @@
|
||||
<script setup>
|
||||
import {NCard, NSkeleton, NFlex, NFormItem, NForm, NInputNumber} from "naive-ui";
|
||||
import {NCard, NSkeleton, NSpace, NFlex, NFormItem, NForm, NInputNumber, NStatistic} from "naive-ui";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {useAuthStore} from "../../../Stores/auth.js";
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const authStore = useAuthStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard>
|
||||
<div class="grid grid-cols-[1fr_auto] gap-x-3">
|
||||
<NCard v-if="reportStore.reportInfo?.report.isActiveSendButton">
|
||||
<NForm>
|
||||
<NFlex>
|
||||
<template v-if="reportStore.isLoadReportInfo">
|
||||
<NSkeleton class="w-[246px]! h-[60px]!" />
|
||||
<NSkeleton class="w-[246px]! h-[60px]!" />
|
||||
<NSkeleton class="w-[246px]! h-[60px]!" />
|
||||
<NFlex>
|
||||
<NSkeleton class="rounded-md w-[246px]! h-[65px]!" />
|
||||
<NSkeleton class="rounded-md w-[246px]! h-[65px]!" />
|
||||
<NSkeleton class="rounded-md w-[246px]! h-[65px]!" />
|
||||
</NFlex>
|
||||
</template>
|
||||
<NFlex v-else justify="space-between" align="center">
|
||||
<NSpace>
|
||||
<template v-for="metrikaItem in reportStore.reportInfo?.metrikaItems">
|
||||
<NFormItem :label="metrikaItem.name" :show-feedback="false">
|
||||
<NInputNumber v-model:value="reportStore.reportForm[`metrika_item_${metrikaItem.metrika_item_id}`]"
|
||||
:default-value="metrikaItem.default_value" />
|
||||
</NFormItem>
|
||||
</template>
|
||||
</NSpace>
|
||||
</NFlex>
|
||||
</NForm>
|
||||
</NCard>
|
||||
<NSpace :wrap="false">
|
||||
<NCard class="min-w-[120px] max-w-[120px] min-h-[100px] max-h-[102px] h-full"
|
||||
style="--n-padding-top: 0; --n-padding-left: 0; --n-padding-bottom: 0; --n-padding-right: 0;"
|
||||
>
|
||||
<div class="w-full h-full flex flex items-center justify-center">
|
||||
<NStatistic label="Умерло" :value="reportStore.reportInfo?.department.deadCount" />
|
||||
</div>
|
||||
</NCard>
|
||||
<NCard class="min-w-[120px] max-w-[120px] min-h-[100px] max-h-[102px] h-full"
|
||||
style="--n-padding-top: 0; --n-padding-bottom: 0; --n-padding-left: 8px; --n-padding-right: 8px;">
|
||||
<div class="w-full h-full flex flex items-center justify-center">
|
||||
<NStatistic :value="reportStore.reportInfo?.department.surgicalCount[1]">
|
||||
<template #label>
|
||||
<div class="flex flex-col">
|
||||
<span>Операций</span>
|
||||
<span>Э / П</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #suffix>
|
||||
/ {{ reportStore.reportInfo?.department.surgicalCount[0] }}
|
||||
</template>
|
||||
</NStatistic>
|
||||
</div>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
:deep(.n-statistic__label),
|
||||
:deep(.n-statistic-value__content) {
|
||||
@apply text-center;
|
||||
}
|
||||
:deep(.n-statistic-value) {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {ru} from "date-fns/locale";
|
||||
import {useAuthStore} from "../../../Stores/auth.js";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {RiAddCircleLine} from 'vue-icons-plus/ri'
|
||||
import {TbAlertCircle} from 'vue-icons-plus/tb'
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import ReportSelectDate from "../../../Components/ReportSelectDate.vue";
|
||||
import DepartmentSelect from "../../../Components/DepartmentSelect.vue";
|
||||
@@ -53,19 +54,16 @@ const currentDate = computed(() => {
|
||||
<template>
|
||||
<NCard>
|
||||
<NFlex vertical>
|
||||
<NFlex align="center" justify="space-between" :wrap="false">
|
||||
<div class="grid grid-cols-[auto_1fr_auto] items-center">
|
||||
<NTag v-if="isFillableMode" type="info" :bordered="false">
|
||||
{{ authStore.userDepartment.name_full }}
|
||||
</NTag>
|
||||
<DepartmentSelect v-if="isReadonlyMode" />
|
||||
|
||||
<NFlex align="center" :wrap="false">
|
||||
<NH2 v-if="isFillableMode" class="mb-0!">
|
||||
{{ currentDate }}
|
||||
</NH2>
|
||||
<ReportSelectDate v-if="isReadonlyMode" />
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
<div class="col-3 w-full">
|
||||
<ReportSelectDate />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NFlex justify="space-between" align="center" :wrap="false">
|
||||
<NRow class="grow">
|
||||
@@ -113,9 +111,9 @@ const currentDate = computed(() => {
|
||||
|
||||
<NButton type="error" secondary @click="openUnwantedEventModal = true">
|
||||
<template #icon>
|
||||
<RiAddCircleLine />
|
||||
<TbAlertCircle />
|
||||
</template>
|
||||
Нежелательное событие
|
||||
Нежелательные события ({{ reportStore.unwantedEvents.length }})
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
@@ -125,5 +123,11 @@ const currentDate = computed(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
:deep(.n-statistic__label),
|
||||
:deep(.n-statistic-value__content) {
|
||||
@apply text-center;
|
||||
}
|
||||
:deep(.n-statistic-value) {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import {NCard, NFlex, NAlert, NCollapse, NCollapseItem} from 'naive-ui'
|
||||
import {NCard, NFlex, NAlert, NCollapse, NCollapseItem, NTabPane, NTabs} from 'naive-ui'
|
||||
import ReportSectionItem from "./ReportSectionItem.vue";
|
||||
import {computed} from "vue";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
@@ -79,12 +79,37 @@ const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
|
||||
</NCollapseItem>
|
||||
<NCollapseItem name="4">
|
||||
<template #header>
|
||||
<ReportSectionHeader title="Умершие" status="deceased" />
|
||||
<ReportSectionHeader title="Выбывшие" status="outcome" />
|
||||
</template>
|
||||
<ReportSectionItem status="deceased"
|
||||
<NTabs type="segment" animated>
|
||||
<NTabPane name="transferred">
|
||||
<template #tab>
|
||||
<ReportSectionHeader title="Переведённые" status="outcome-transferred" />
|
||||
</template>
|
||||
<ReportSectionItem status="outcome-transferred"
|
||||
@item-dragged="handleItemDragged"
|
||||
@item-dropped="handleItemDropped"
|
||||
/>
|
||||
</NTabPane>
|
||||
<NTabPane name="discharged">
|
||||
<template #tab>
|
||||
<ReportSectionHeader title="Выписанные" status="outcome-discharged" />
|
||||
</template>
|
||||
<ReportSectionItem status="outcome-discharged"
|
||||
@item-dragged="handleItemDragged"
|
||||
@item-dropped="handleItemDropped"
|
||||
/>
|
||||
</NTabPane>
|
||||
<NTabPane name="deceased">
|
||||
<template #tab>
|
||||
<ReportSectionHeader title="Умершие" status="outcome-deceased" />
|
||||
</template>
|
||||
<ReportSectionItem status="outcome-deceased"
|
||||
@item-dragged="handleItemDragged"
|
||||
@item-dropped="handleItemDropped"
|
||||
/>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
|
||||
@@ -19,7 +19,6 @@ const reportStore = useReportStore()
|
||||
const isLoading = ref(true)
|
||||
const countPatient = ref(null)
|
||||
const fetchPatientCount = async () => {
|
||||
if (props.status === 'plan' || props.status === 'emergency') {
|
||||
isLoading.value = true
|
||||
const data = {
|
||||
status: props.status,
|
||||
@@ -31,9 +30,6 @@ const fetchPatientCount = async () => {
|
||||
}).finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
} else {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const computedHeader = computed(() => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup>
|
||||
import {NIcon, NText, NDataTable, NButton} from "naive-ui";
|
||||
import {NIcon, NText, NDataTable, NButton, NTabs, NTabPane} from "naive-ui";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {computed, h, onMounted, ref, watch} from "vue";
|
||||
import { VueDraggableNext } from 'vue-draggable-next'
|
||||
import {storeToRefs} from "pinia";
|
||||
import {TbGripVertical} from "vue-icons-plus/tb";
|
||||
import MoveModalComment from "./MoveModalComment.vue";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
@@ -30,7 +31,7 @@ const props = defineProps({
|
||||
accentIds: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
|
||||
@@ -46,10 +47,12 @@ const {patientsData} = storeToRefs(reportStore)
|
||||
const baseColumns = reportStore.getColumnsByKey(props.keys)
|
||||
const data = ref([])
|
||||
const isLoading = ref(true)
|
||||
const showMoveModal = ref(false)
|
||||
const latestDropItem = ref(null)
|
||||
|
||||
// Добавляем drag колонку если режим fillable
|
||||
const columns = computed(() => {
|
||||
if (!isFillableMode.value) return baseColumns
|
||||
// if (!isFillableMode.value) return baseColumns
|
||||
|
||||
const newColumns = []
|
||||
|
||||
@@ -83,7 +86,11 @@ const columns = computed(() => {
|
||||
{
|
||||
text: true,
|
||||
onClick: () => {
|
||||
alert('message')
|
||||
axios.post('/api/report/observation/remove', {
|
||||
id: row.id
|
||||
}).then(async () => {
|
||||
await fetchPatients()
|
||||
})
|
||||
}
|
||||
},
|
||||
[
|
||||
@@ -92,11 +99,30 @@ const columns = computed(() => {
|
||||
)
|
||||
}
|
||||
|
||||
const expandColumn = {
|
||||
type: 'expand',
|
||||
renderExpand: (rowData) => {
|
||||
return h(
|
||||
NText,
|
||||
{
|
||||
class: 'max-w-full break-words whitespace-normal'
|
||||
},
|
||||
{
|
||||
default: rowData.comment ?? 'Причина наблюдения не указана'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (props.status === 'observation') {
|
||||
newColumns.push(expandColumn)
|
||||
}
|
||||
|
||||
if (props.isDraggable) newColumns.push(dragColumn)
|
||||
newColumns.push(...baseColumns)
|
||||
if (props.isRemovable) newColumns.push(removeColumn)
|
||||
|
||||
if (props.status === 'emergency') {
|
||||
if (props.status === 'emergency' || props.status === 'plan') {
|
||||
const operationColumn = {
|
||||
title: 'Операции',
|
||||
key: 'operations',
|
||||
@@ -109,11 +135,25 @@ const columns = computed(() => {
|
||||
return `${itm.code}; `
|
||||
})
|
||||
]
|
||||
) : h('div', {}, '-')
|
||||
) : h('div', {}, '-'),
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
}
|
||||
}
|
||||
newColumns.push(operationColumn)
|
||||
}
|
||||
|
||||
if (props.status === 'outcome') {
|
||||
const typeColumn = {
|
||||
title: 'Причина',
|
||||
key: 'outcome_type',
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
}
|
||||
}
|
||||
newColumns.push(typeColumn)
|
||||
}
|
||||
|
||||
return newColumns
|
||||
})
|
||||
|
||||
@@ -156,6 +196,9 @@ const handleDrop = (e) => {
|
||||
fromStatus: dragData.fromStatus,
|
||||
toStatus: props.status
|
||||
})
|
||||
|
||||
latestDropItem.value = dragData.row
|
||||
showMoveModal.value = true
|
||||
} catch (error) {
|
||||
console.error('Drop error:', error)
|
||||
}
|
||||
@@ -226,14 +269,11 @@ onMounted(async () => {
|
||||
max-height="200"
|
||||
min-height="200"
|
||||
:row-props="rowProps"
|
||||
:row-key="(row) => row.id"
|
||||
class="text-sm!">
|
||||
</NDataTable>
|
||||
<!-- <NDataTable :columns="columns"-->
|
||||
<!-- :data="data"-->
|
||||
<!-- size="small"-->
|
||||
<!-- max-height="200"-->
|
||||
<!-- class="text-sm!">-->
|
||||
<!-- </NDataTable>-->
|
||||
|
||||
<MoveModalComment v-model:show="showMoveModal" :patient-id="latestDropItem?.id" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script setup>
|
||||
import {NModal, NForm, NFormItem, NInput, NFlex, NButton} from 'naive-ui'
|
||||
import {useForm} from "@inertiajs/vue3";
|
||||
import {NModal, NList, NListItem, NThing, NAvatar, NIcon, NDrawer, NDrawerContent,
|
||||
NText, NDivider, NForm, NFormItem, NInput, NFlex, NButton, NScrollbar, NEmpty
|
||||
} from 'naive-ui'
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {ref} from "vue";
|
||||
const open = defineModel('open')
|
||||
import { TbAlertCircle, TbPencil, TbTrashX, TbCirclePlus, TbCheck, TbX } from 'vue-icons-plus/tb'
|
||||
import {format, isValid} from "date-fns";
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const formRef = ref()
|
||||
const createDrawerShow = ref(false)
|
||||
const rules = {
|
||||
comment: {
|
||||
required: true,
|
||||
@@ -14,34 +18,191 @@ const rules = {
|
||||
trigger: 'blur'
|
||||
}
|
||||
}
|
||||
const selectedEvent = ref(null)
|
||||
const drawerCreatingMode = ref(true) // or false = editing
|
||||
|
||||
const onSubmit = () => {
|
||||
// Создание в сторе и открытие drawer с формой нежелательного события
|
||||
const onCreateEvent = () => {
|
||||
drawerCreatingMode.value = true
|
||||
const createDate = format(new Date(), 'Создано dd.MM.yyyy в HH:mm')
|
||||
reportStore.unwantedEvents.push({
|
||||
title: `Нежелательное событие №${reportStore.unwantedEvents.length + 1}`,
|
||||
comment: '',
|
||||
created_at: createDate
|
||||
})
|
||||
|
||||
const length = reportStore.unwantedEvents.length
|
||||
|
||||
selectedEvent.value = reportStore.unwantedEvents[length - 1]
|
||||
createDrawerShow.value = true
|
||||
}
|
||||
|
||||
const onEditEvent = (event) => {
|
||||
drawerCreatingMode.value = false
|
||||
selectedEvent.value = event
|
||||
createDrawerShow.value = true
|
||||
}
|
||||
|
||||
const onDeleteEvent = (event) => {
|
||||
const indexOfDelete = reportStore.unwantedEvents.findIndex(itm => itm === event)
|
||||
|
||||
if (typeof event.unwanted_event_id !== 'undefined') {
|
||||
axios.delete(`/api/report/unwanted-event/${event.unwanted_event_id}`)
|
||||
.then(() => {
|
||||
reportStore.unwantedEvents.splice(indexOfDelete, 1)
|
||||
})
|
||||
} else {
|
||||
reportStore.unwantedEvents.splice(indexOfDelete, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const onCancelDrawerEvent = (event) => {
|
||||
onDeleteEvent(event)
|
||||
createDrawerShow.value = false
|
||||
}
|
||||
|
||||
const onCreateDrawerEvent = () => {
|
||||
formRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
open.value = false
|
||||
console.log(createDrawerShow.value)
|
||||
createDrawerShow.value = false
|
||||
console.log(createDrawerShow.value)
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onBeforeLeaveModal = () => {
|
||||
selectedEvent.value = null
|
||||
drawerCreatingMode.value = true
|
||||
createDrawerShow.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="open" title="Нежелательное событие" preset="card" class="max-w-xl">
|
||||
<NForm ref="formRef" :model="reportStore.reportForm" :rules="rules">
|
||||
<NFormItem :show-label="false" path="comment">
|
||||
<NInput type="textarea" :rows="8" v-model:value="reportStore.reportForm.comment" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<NModal v-model:show="open"
|
||||
title="Нежелательные события"
|
||||
preset="card"
|
||||
:mask-closable="false"
|
||||
:close-on-esc="false"
|
||||
@before-leave="onBeforeLeaveModal"
|
||||
class="max-w-4xl overflow-clip h-[calc(100vh-220px)]"
|
||||
>
|
||||
<template v-if="reportStore.unwantedEvents.length">
|
||||
<NScrollbar class="max-h-[calc(100vh-282px)] pr-3">
|
||||
<NList>
|
||||
<NListItem v-for="event in reportStore.unwantedEvents">
|
||||
<NThing>
|
||||
<template #avatar>
|
||||
<NAvatar>
|
||||
<NIcon>
|
||||
<TbAlertCircle class="text-red-400" />
|
||||
</NIcon>
|
||||
</NAvatar>
|
||||
</template>
|
||||
<template #header>
|
||||
{{ event.title }}
|
||||
</template>
|
||||
<template #description>
|
||||
<NText depth="3">
|
||||
{{ event.created_at }}
|
||||
</NText>
|
||||
</template>
|
||||
<NText>
|
||||
{{ event.comment }}
|
||||
</NText>
|
||||
<template #action>
|
||||
<NFlex align="center" justify="end">
|
||||
<NButton type="primary" tertiary @click="onSubmit">
|
||||
Сохранить
|
||||
<NFlex align="center">
|
||||
<NButton secondary size="small" @click="onEditEvent(event)">
|
||||
<template #icon>
|
||||
<TbPencil />
|
||||
</template>
|
||||
Редактировать
|
||||
</NButton>
|
||||
<NDivider vertical />
|
||||
<NButton type="error" secondary size="small" @click="onDeleteEvent(event)">
|
||||
<template #icon>
|
||||
<TbTrashX />
|
||||
</template>
|
||||
Удалить
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NThing>
|
||||
</NListItem>
|
||||
</NList>
|
||||
</NScrollbar>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<NEmpty description="Нежелательных событий не найдено!">
|
||||
<template #extra>
|
||||
<NButton type="primary" secondary @click="onCreateEvent()" size="small">
|
||||
<template #icon>
|
||||
<TbCirclePlus />
|
||||
</template>
|
||||
Создать
|
||||
</NButton>
|
||||
</template>
|
||||
</NEmpty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #action>
|
||||
<NFlex id="modal-action" align="center" justify="space-between">
|
||||
<NButton type="primary" secondary @click="onCreateEvent()">
|
||||
<template #icon>
|
||||
<TbCirclePlus />
|
||||
</template>
|
||||
Создать событие
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
<NDrawer
|
||||
:show="createDrawerShow"
|
||||
placement="bottom"
|
||||
:max-height="600"
|
||||
:min-height="400"
|
||||
:default-height="400"
|
||||
resizable
|
||||
:trap-focus="false"
|
||||
:block-scroll="false"
|
||||
:mask-closable="false"
|
||||
to="#modal-action"
|
||||
>
|
||||
<NDrawerContent>
|
||||
<template #header>
|
||||
<template v-if="drawerCreatingMode">Создание события</template>
|
||||
<template v-else>Редактирование события</template>
|
||||
</template>
|
||||
<NForm ref="formRef" :model="selectedEvent" :rules="rules">
|
||||
<NFormItem :show-label="false" path="comment">
|
||||
<NInput type="textarea" :rows="8" v-model:value="selectedEvent.comment" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NFlex align="center">
|
||||
<NButton v-if="drawerCreatingMode" type="error" secondary @click="onCancelDrawerEvent(selectedEvent)">
|
||||
<template #icon>
|
||||
<TbX />
|
||||
</template>
|
||||
Отменить создание
|
||||
</NButton>
|
||||
<NDivider v-if="drawerCreatingMode" vertical />
|
||||
<NButton type="primary" @click="onCreateDrawerEvent">
|
||||
<template #icon>
|
||||
<TbCheck />
|
||||
</template>
|
||||
<template v-if="drawerCreatingMode">Создать</template>
|
||||
<template v-else>Сохранить</template>
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -55,11 +55,14 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
plan: [],
|
||||
emergency: [],
|
||||
observation: [],
|
||||
deceased: []
|
||||
outcome: []
|
||||
})
|
||||
|
||||
const reportForm = ref({})
|
||||
|
||||
// Нежелательные события
|
||||
const unwantedEvents = ref([])
|
||||
|
||||
const getColumnsByKey = (keys) => {
|
||||
const result = []
|
||||
for (const key of keys) {
|
||||
@@ -74,9 +77,8 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
const form = {
|
||||
metrics: reportForm.value,
|
||||
observationPatients: patientsData.value['observation'],
|
||||
unwantedEvent: {
|
||||
comment: reportForm.comment
|
||||
},
|
||||
unwantedEvents: unwantedEvents.value,
|
||||
dates: timestampCurrentRange.value,
|
||||
...assignForm
|
||||
}
|
||||
|
||||
@@ -90,6 +92,7 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
|
||||
const resetReportForm = () => {
|
||||
reportForm.value = {}
|
||||
unwantedEvents.value = []
|
||||
patientsData.value.observation = []
|
||||
}
|
||||
|
||||
@@ -107,6 +110,8 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
reportForm.value.metrika_item_7 = reportInfo.value.department?.extractCount
|
||||
reportForm.value.metrika_item_8 = reportInfo.value.department?.currentCount
|
||||
|
||||
unwantedEvents.value = reportInfo.value.report.unwantedEvents
|
||||
|
||||
timestampCurrentRange.value = [
|
||||
reportInfo.value.dates.startAt,
|
||||
reportInfo.value.dates.endAt,
|
||||
@@ -128,6 +133,8 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
reportForm.value.metrika_item_7 = reportInfo.value.department?.extractCount
|
||||
reportForm.value.metrika_item_8 = reportInfo.value.department?.currentCount
|
||||
|
||||
unwantedEvents.value = reportInfo.value.report.unwantedEvents
|
||||
|
||||
timestampCurrentRange.value = [
|
||||
reportInfo.value.dates.startAt,
|
||||
reportInfo.value.dates.endAt,
|
||||
@@ -148,6 +155,7 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
patientsData,
|
||||
reportInfo,
|
||||
reportForm,
|
||||
unwantedEvents,
|
||||
|
||||
getColumnsByKey,
|
||||
getDataOnReportDate,
|
||||
|
||||
@@ -47,6 +47,8 @@ Route::middleware(['auth:sanctum'])->group(function () {
|
||||
Route::prefix('report')->group(function () {
|
||||
Route::get('/', [ReportController::class, 'index']);
|
||||
Route::post('/', [ReportController::class, 'store']);
|
||||
Route::post('/observation/remove', [ReportController::class, 'removeObservation']);
|
||||
Route::delete('/unwanted-event/{unwantedEvent}', [ReportController::class, 'removeUnwantedEvent']);
|
||||
});
|
||||
|
||||
Route::prefix('app')->group(function () {
|
||||
|
||||
Reference in New Issue
Block a user