* восстановление окна наблюдения

* добавил получение выбывших
* фильтрация выбывших по результатам лечения
* добавил подсказку при наведении на операции
* добавил вывод причины наблюдения
* добавил вкладки для выбывших
* изменил связь и сохранение пациентов на контроле
* добавил возможность редактирования причины контроля
* полное изменение окна с нежелательными событиями
* исправил просмотр причины контроля
* работа над окном редактирования причины контроля в таблице
* визуальное выделение умерших и проведенных операций
* добавил выбор даты для роли врач
* центрирование блоков статистики
* разделение выполненных операций на срочность
* поправил метод определения текущего дня для роли врач
* функция блокировки при выборе другой даты для роли врач
This commit is contained in:
brusnitsyn
2026-01-29 16:42:42 +09:00
parent cb43c74a72
commit 87e21f0e08
24 changed files with 2065 additions and 501 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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,

View File

@@ -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)
];
}
}

View 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');
}
}

View File

@@ -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);
}
/*

View File

@@ -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');

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View 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');
}
}

View 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();
}
}

View 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
]);
}
}
}
}

View File

@@ -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');
});
}
};

View File

@@ -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');
}
};

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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(() => {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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 () {