diff --git a/app/Console/Commands/FillAverageBedDaysMetric.php b/app/Console/Commands/FillAverageBedDaysMetric.php
index 2aae887..2396c4b 100644
--- a/app/Console/Commands/FillAverageBedDaysMetric.php
+++ b/app/Console/Commands/FillAverageBedDaysMetric.php
@@ -45,12 +45,12 @@ class FillAverageBedDaysMetric extends Command
// Filter by date range
if ($from = $this->option('from')) {
- $query->whereDate('created_at', '>=', $from);
+ $query->where('period_start', '>=', Carbon::parse($from, 'Asia/Yakutsk')->startOfDay()->format('Y-m-d H:i:s'));
$this->info("Filter: from {$from}");
}
if ($to = $this->option('to')) {
- $query->whereDate('created_at', '<=', $to);
+ $query->where('period_end', '<', Carbon::parse($to, 'Asia/Yakutsk')->addDay()->startOfDay()->format('Y-m-d H:i:s'));
$this->info("Filter: to {$to}");
}
@@ -147,10 +147,10 @@ class FillAverageBedDaysMetric extends Command
// Apply same filters to sample
if ($from = $this->option('from')) {
- $sampleQuery->whereDate('created_at', '>=', $from);
+ $sampleQuery->where('period_start', '>=', Carbon::parse($from, 'Asia/Yakutsk')->startOfDay()->format('Y-m-d H:i:s'));
}
if ($to = $this->option('to')) {
- $sampleQuery->whereDate('created_at', '<=', $to);
+ $sampleQuery->where('period_end', '<', Carbon::parse($to, 'Asia/Yakutsk')->addDay()->startOfDay()->format('Y-m-d H:i:s'));
}
if ($departmentId = $this->option('department')) {
$sampleQuery->where('rf_department_id', $departmentId);
diff --git a/app/Console/Commands/RecalculatePreoperativeMetric.php b/app/Console/Commands/RecalculatePreoperativeMetric.php
index dab0231..08df419 100644
--- a/app/Console/Commands/RecalculatePreoperativeMetric.php
+++ b/app/Console/Commands/RecalculatePreoperativeMetric.php
@@ -56,12 +56,12 @@ class RecalculatePreoperativeMetric extends Command
// Фильтр по дате
if ($from = $this->option('from')) {
- $query->whereDate('created_at', '>=', $from);
+ $query->where('period_start', '>=', Carbon::parse($from, 'Asia/Yakutsk')->startOfDay()->format('Y-m-d H:i:s'));
$this->info("📅 Фильтр: с {$from}");
}
if ($to = $this->option('to')) {
- $query->whereDate('created_at', '<=', $to);
+ $query->where('period_end', '<', Carbon::parse($to, 'Asia/Yakutsk')->addDay()->startOfDay()->format('Y-m-d H:i:s'));
$this->info("📅 Фильтр: по {$to}");
}
diff --git a/app/Data/UnifiedPatientData.php b/app/Data/UnifiedPatientData.php
index f84fa44..d5e9f49 100644
--- a/app/Data/UnifiedPatientData.php
+++ b/app/Data/UnifiedPatientData.php
@@ -43,11 +43,15 @@ class UnifiedPatientData
$birthDate = $birthDateValue?->format('Y-m-d');
$manualId = $linkedManualPatient?->department_patient_id;
$outcomeMigration = $patient->relationLoaded('outcomeMigration')
- ? $patient->outcomeMigration->first()
+ ? $patient->outcomeMigration()->first()
: null;
+
$migration = $patient->relationLoaded('migrations')
? $patient->migrations->first()
: null;
+ if (!$migration && $patient->relationLoaded('latestMigration')) {
+ $migration = $patient->latestMigration;
+ }
$diagnosisMkb = $outcomeMigration?->mainDiagnosis?->mkb ?? $migration?->mainDiagnosis?->mkb;
$operations = $patient->relationLoaded('surgicalOperations')
? $patient->surgicalOperations->map(fn ($operation) => [
@@ -91,7 +95,7 @@ class UnifiedPatientData
$manualId = $linkedManualPatient?->department_patient_id;
$medicalHistory = $migration->medicalHistory;
$outcomeMigration = $medicalHistory->relationLoaded('outcomeMigration')
- ? $medicalHistory->outcomeMigration->first()
+ ? $medicalHistory->outcomeMigration()->first()
: null;
$operations = $medicalHistory->relationLoaded('surgicalOperations')
? $medicalHistory->surgicalOperations->map(fn ($operation) => [
@@ -164,6 +168,9 @@ class UnifiedPatientData
{
$birthDate = self::normalizeDate($snapshot->birth_date);
+// if ($snapshot->rf_medicalhistory_id === 334148)
+// dd($snapshot);
+
return new self(
id: $snapshot->rf_department_patient_id ? "manual:{$snapshot->rf_department_patient_id}" : ($snapshot->patient_uid ?: "mis:{$snapshot->rf_medicalhistory_id}"),
patientUid: $snapshot->patient_uid ?: ($snapshot->rf_medicalhistory_id ? "mis:{$snapshot->rf_medicalhistory_id}" : "snapshot:{$snapshot->medical_history_snapshot_id}"),
diff --git a/app/Http/Controllers/Api/MetrikaFormController.php b/app/Http/Controllers/Api/MetrikaFormController.php
index 14ceee9..87e216c 100644
--- a/app/Http/Controllers/Api/MetrikaFormController.php
+++ b/app/Http/Controllers/Api/MetrikaFormController.php
@@ -10,6 +10,7 @@ use App\Models\MetrikaResult;
use App\Models\MetrikaResultValue;
use App\Models\Report;
use Illuminate\Http\Request;
+use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
@@ -66,11 +67,14 @@ class MetrikaFormController extends Controller
}
// Создаем или обновляем отчет
+ [$periodStart, $periodEnd] = $this->getTodayPeriodBounds();
$report = Report::create(
[
'rf_user_id' => $user->id,
- 'created_at' => now()->toDateString(),
- 'sent_at' => now()->toDateString(),
+ 'created_at' => $periodEnd,
+ 'sent_at' => $periodEnd,
+ 'period_start' => $periodStart,
+ 'period_end' => $periodEnd,
'rf_department_id' => $user->department->departmentid
]
);
@@ -150,7 +154,7 @@ class MetrikaFormController extends Controller
// Находим последний отчет пользователя за сегодня
$report = Report::where('rf_user_id', $user->id)
- ->whereDate('created_at', now()->toDateString())
+ ->exactPeriod(...$this->getTodayPeriodBounds())
->orderBy('created_at', 'desc')
->first();
@@ -252,6 +256,7 @@ class MetrikaFormController extends Controller
$dateStart = date('Y-m-d', $startAt) . ' 00:00:00';
$dateEnd = date('Y-m-d', $endAt) . ' 23:59:59';
+ $dateEndExclusive = Carbon::parse($dateEnd, 'Asia/Yakutsk')->addSecond()->format('Y-m-d H:i:s');
$group = MetrikaGroup::findOrFail($groupId);
@@ -260,9 +265,8 @@ class MetrikaFormController extends Controller
->join('metrika_result_values as mv', 'mr.metrika_result_id', '=', 'mv.rf_metrika_result_id')
->join('reports as r', 'mr.rf_report_id', '=', 'r.report_id')
->where('mr.rf_metrika_group_id', $groupId)
-// ->whereBetween('r.sent_at', [$dateStart, $dateEnd])
- ->where('r.sent_at', '>', $dateStart)
- ->where('r.sent_at', '<=', $dateEnd)
+ ->where('r.period_start', '>=', $dateStart)
+ ->where('r.period_end', '<', $dateEndExclusive)
->when(!$user->isAdmin() && !$user->isHeadOfDepartment(), function ($query) use ($user) {
return $query->where('r.rf_user_id', $user->id);
})
@@ -355,9 +359,8 @@ class MetrikaFormController extends Controller
$endDate = date("{$year}-{$month}-t", strtotime($startDate));
$reports = Report::where('rf_user_id', $user->id)
-// ->whereBetween('sent_at', [$startDate, $endDate])
- ->where('sent_at', '>', $startDate)
- ->where('sent_at', '<=', $endDate)
+ ->where('period_start', '>=', $startDate)
+ ->where('period_end', '<', Carbon::parse($endDate, 'Asia/Yakutsk')->addDay()->startOfDay()->format('Y-m-d H:i:s'))
->get();
// Создаем календарь
@@ -406,7 +409,7 @@ class MetrikaFormController extends Controller
// Дни месяца
for ($day = 1; $day <= $daysInMonth; $day++) {
$date = date("{$year}-{$month}-" . sprintf('%02d', $day));
- $report = $reports->firstWhere('sent_at', $date);
+ $report = $reports->first(fn (Report $item) => $item->period_end && $item->period_end->toDateString() === $date);
$timestamp = strtotime($date) * 1000; // В миллисекундах
$dayData = [
@@ -420,7 +423,7 @@ class MetrikaFormController extends Controller
'is_weekend' => date('N', strtotime($date)) >= 6,
'has_report' => !is_null($report),
'report_status' => $report ? $report->status : null,
- 'sent_at' => $report && $report->sent_at ? $report->sent_at->getTimestamp() * 1000 : $timestamp
+ 'sent_at' => $report && $report->period_end ? $report->period_end->getTimestamp() * 1000 : $timestamp
];
$calendar['days'][] = $dayData;
@@ -564,7 +567,7 @@ class MetrikaFormController extends Controller
$user = Auth::user();
$report = Report::where('rf_user_id', $user->id)
- ->whereDate('created_at', now()->toDateString())
+ ->exactPeriod(...$this->getTodayPeriodBounds())
->first();
if (!$report) {
@@ -593,4 +596,14 @@ class MetrikaFormController extends Controller
];
}
+ private function getTodayPeriodBounds(): array
+ {
+ $now = now('Asia/Yakutsk');
+
+ return [
+ $now->copy()->subDay()->setTime(7, 0)->format('Y-m-d H:i:s'),
+ $now->copy()->setTime(7, 0)->format('Y-m-d H:i:s'),
+ ];
+ }
+
}
diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php
index 4b2013a..cc7e12b 100644
--- a/app/Http/Controllers/Api/ReportController.php
+++ b/app/Http/Controllers/Api/ReportController.php
@@ -244,8 +244,10 @@ class ReportController extends Controller
'rf_department_id' => $data['departmentId'],
'rf_user_id' => Auth::user()->id,
'rf_lpudoctor_id' => $data['userId'],
- 'created_at' => now(),
- 'sent_at' => now()
+ 'created_at' => $dateRange->endSql(),
+ 'sent_at' => $dateRange->endSql(),
+ 'period_start' => $dateRange->startSql(),
+ 'period_end' => $dateRange->endSql(),
]
);
} else {
@@ -253,8 +255,10 @@ class ReportController extends Controller
'rf_department_id' => $data['departmentId'],
'rf_user_id' => Auth::user()->id,
'rf_lpudoctor_id' => $data['userId'],
- 'created_at' => now(),
- 'sent_at' => now()
+ 'created_at' => $dateRange->endSql(),
+ 'sent_at' => $dateRange->endSql(),
+ 'period_start' => $dateRange->startSql(),
+ 'period_end' => $dateRange->endSql(),
]);
}
@@ -869,6 +873,10 @@ class ReportController extends Controller
$user = Auth::user();
$data = $request->validate([
'departmentId' => 'required|integer',
+ 'report_id' => 'nullable|integer',
+ 'startAt' => 'required_without:report_id',
+ 'endAt' => 'required_without:report_id',
+ 'user_id' => 'nullable|integer',
'full_name' => 'required|string|max:255',
'birth_date' => 'required|date',
'patient_kind' => 'required|in:plan,emergency',
@@ -880,18 +888,22 @@ class ReportController extends Controller
$department = Department::where('department_id', $data['departmentId'])->firstOrFail();
$patient = $this->reportService->createManualPatient($department, $user, $data);
- return response()->json($patient, 201);
+ return response()->json([
+ 'patient' => $patient,
+ 'report_id' => $patient->rf_report_id,
+ ], 201);
}
public function setManualPatientOutcome(Request $request, int $departmentPatientId)
{
+ $user = Auth::user();
$data = $request->validate([
'outcome_type' => 'required|in:discharged,transferred,deceased',
'outcome_at' => 'nullable|date',
]);
return response()->json(
- $this->reportService->setManualPatientOutcome($departmentPatientId, $data)
+ $this->reportService->setManualPatientOutcome($user, $departmentPatientId, $data)
);
}
@@ -903,6 +915,8 @@ class ReportController extends Controller
'full_name' => 'required|string|max:255',
'birth_date' => 'required|date',
'patient_kind' => 'required|in:plan,emergency',
+ 'manual_status' => 'nullable|in:current,discharged,deceased',
+ 'outcome_at' => 'nullable|date',
'diagnosis_code' => 'nullable|string|max:255',
'diagnosis_name' => 'nullable|string|max:1000',
'admitted_at' => 'nullable|date',
@@ -1087,15 +1101,13 @@ class ReportController extends Controller
{
if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 1.0)
return Report::where('rf_department_id', $departmentId)
-// ->whereBetween('created_at', [$startDate, $endDate])
- ->where('sent_at', '>=', $startDate)
- ->where('sent_at', '<=', $endDate)
- ->orderBy('sent_at', 'ASC')
+ ->withinPeriod($startDate, $endDate)
+ ->orderBy('period_end', 'ASC')
->get();
else
return Report::where('rf_department_id', $departmentId)
- ->whereDate('created_at', $endDate)
- ->orderBy('created_at', 'ASC')
+ ->exactPeriod($startDate, $endDate)
+ ->orderBy('period_end', 'ASC')
->get();
}
@@ -1106,12 +1118,12 @@ class ReportController extends Controller
]);
$report = Report::where('rf_department_id', $request->department_id)
- ->whereDate('created_at', now()->toDateString())
+ ->exactPeriod(now('Asia/Yakutsk')->subDay()->setTime(7, 0)->format('Y-m-d H:i:s'), now('Asia/Yakutsk')->setTime(7, 0)->format('Y-m-d H:i:s'))
->first();
return response()->json([
'report_id' => $report?->report_id,
- 'exists' => $report->exists
+ 'exists' => (bool) $report
]);
}
}
diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php
index 9abeb7a..76d0ae7 100644
--- a/app/Http/Controllers/TestController.php
+++ b/app/Http/Controllers/TestController.php
@@ -22,4 +22,11 @@ class TestController extends Controller
'data' => $data
]);
}
+
+ public function testIndex()
+ {
+ return Inertia::render('TestIndex', [
+
+ ]);
+ }
}
diff --git a/app/Http/Controllers/Web/ReportController.php b/app/Http/Controllers/Web/ReportController.php
index 9c1a901..70621b4 100644
--- a/app/Http/Controllers/Web/ReportController.php
+++ b/app/Http/Controllers/Web/ReportController.php
@@ -38,7 +38,8 @@ class ReportController extends Controller
'unwantedEvents' => 'nullable|array',
'dates' => 'required|array',
'userId' => 'required|integer',
- 'reportId' => 'nullable|integer'
+ 'reportId' => 'nullable|integer',
+ 'status' => 'nullable|in:draft,submitted',
]);
$this->reportService->storeReport($validated, Auth::user(), false);
diff --git a/app/Models/DepartmentPatient.php b/app/Models/DepartmentPatient.php
index 31e8f1e..611b1fa 100644
--- a/app/Models/DepartmentPatient.php
+++ b/app/Models/DepartmentPatient.php
@@ -10,6 +10,7 @@ class DepartmentPatient extends Model
protected $fillable = [
'rf_department_id',
+ 'rf_report_id',
'source_type',
'rf_medicalhistory_id',
'full_name',
@@ -52,4 +53,9 @@ class DepartmentPatient extends Model
{
return $this->hasMany(DepartmentPatientOperation::class, 'rf_department_patient_id', 'department_patient_id');
}
+
+ public function report()
+ {
+ return $this->belongsTo(Report::class, 'rf_report_id', 'report_id');
+ }
}
diff --git a/app/Models/MisMedicalHistory.php b/app/Models/MisMedicalHistory.php
index f04c0f8..42b86ea 100644
--- a/app/Models/MisMedicalHistory.php
+++ b/app/Models/MisMedicalHistory.php
@@ -152,15 +152,15 @@ class MisMedicalHistory extends Model
public function outcomeMigration()
{
- return $this->migrations()
- ->whereDate('DateOut', '<>', '2222-01-01')
- ->orderBy('DateOut', 'desc');
+ return $this->hasOne(MisMigrationPatient::class, 'rf_MedicalHistoryID', 'MedicalHistoryID')
+ ->whereDate('DateOut', '=', '2222-01-01');
}
public function latestMigration()
{
return $this->hasOne(MisMigrationPatient::class, 'rf_MedicalHistoryID', 'MedicalHistoryID')
- ->ofMany('DateOut', 'max');
+ ->whereDate('DateOut', '=', '2222-01-01')
+ ->orderBy('DateOut', 'desc');
}
/*
diff --git a/app/Models/MisMigrationPatient.php b/app/Models/MisMigrationPatient.php
index bd219f8..9262f5e 100644
--- a/app/Models/MisMigrationPatient.php
+++ b/app/Models/MisMigrationPatient.php
@@ -28,7 +28,11 @@ class MisMigrationPatient extends Model
public function mainDiagnosis()
{
- return $this->hasOne(MisDiagnos::class, 'rf_MigrationPatientID', 'MigrationPatientID')->where('rf_DiagnosTypeID', 3);
+ return $this->hasOne(MisDiagnos::class, 'rf_MigrationPatientID', 'MigrationPatientID')
+ ->where(function ($query) {
+ $query->where('rf_DiagnosTypeID', 3)
+ ->orWhere('rf_DiagnosTypeID', 7);
+ });
}
public function mkb()
diff --git a/app/Models/MisSurgicalOperation.php b/app/Models/MisSurgicalOperation.php
index f0ad4de..a68fc36 100644
--- a/app/Models/MisSurgicalOperation.php
+++ b/app/Models/MisSurgicalOperation.php
@@ -3,10 +3,12 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
+use LaravelIdea\Helper\App\Models\_IH_MisSurgicalOperation_QB;
class MisSurgicalOperation extends Model
{
private const COMPLETED_OPERATION_STATUS_ID = 3;
+ private const CANCELLED_OPERATION_STATUS_ID = 4;
protected $table = 'stt_surgicaloperation';
protected $primaryKey = 'SurgicalOperationID';
@@ -26,10 +28,47 @@ class MisSurgicalOperation extends Model
return $this->hasOne(MisOperationPurpose::class, 'rf_SurgicalOperationID', 'SurgicalOperationID');
}
+
+ /**
+ * Получить операции у которых исход <> 0
+ * @param $query
+ * @return _IH_MisSurgicalOperation_QB|_IH_MisSurgicalOperation_QB
+ */
public function scopeCompleted($query)
+ {
+ return $query->whereNot('rf_OperationResultID', 0);
+ }
+
+ /**
+ * Получить операции с благоприятным исходом
+ * @param $query
+ * @return _IH_MisSurgicalOperation_QB|_IH_MisSurgicalOperation_QB
+ */
+ public function scopePositiveResult($query)
+ {
+ return $query->where('rf_OperationResultID', 1);
+ }
+
+ /**
+ * Получить операции с неблагоприятным исходом
+ * @param $query
+ * @return _IH_MisSurgicalOperation_QB|_IH_MisSurgicalOperation_QB
+ */
+ public function scopeNotPositiveResult($query)
+ {
+ return $query->where('rf_OperationResultID', 2);
+ }
+
+
+ /**
+ * Получить отмененные операции через назначение
+ * @param $query
+ * @return _IH_MisSurgicalOperation_QB
+ */
+ public function scopeCanceled($query)
{
return $query->whereHas('operationPurpose', function ($purposeQuery) {
- $purposeQuery->where('rf_OperationStatusID', self::COMPLETED_OPERATION_STATUS_ID);
+ $purposeQuery->where('rf_OperationStatusID', self::CANCELLED_OPERATION_STATUS_ID);
});
}
}
diff --git a/app/Models/Report.php b/app/Models/Report.php
index 7b7fb91..797bd8e 100644
--- a/app/Models/Report.php
+++ b/app/Models/Report.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
@@ -29,6 +30,32 @@ class Report extends Model
'status',
];
+ protected $casts = [
+ 'created_at' => 'datetime',
+ 'sent_at' => 'datetime',
+ 'period_start' => 'datetime',
+ 'period_end' => 'datetime',
+ ];
+
+ public function scopeWithinPeriod(Builder $query, string $startAt, string $endAt): Builder
+ {
+ return $query
+ ->where('period_start', '>=', $startAt)
+ ->where('period_start', '<=', $endAt);
+ }
+
+ public function scopeExactPeriod(Builder $query, string $startAt, string $endAt): Builder
+ {
+ return $query
+ ->where('period_start', '>=', $startAt)
+ ->where('period_end', '<=', $endAt);
+ }
+
+ public function scopeOnlySubmitted(Builder $query): Builder
+ {
+ return $query->where('status', 'submitted');
+ }
+
public function metrikaResults(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(MetrikaResult::class, 'rf_report_id', 'report_id');
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 452e6b6..56f4373 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -2,6 +2,8 @@
namespace App\Providers;
+use App\Services\Cache\CacheInvalidator;
+use App\Services\Cache\CacheKeyBuilder;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -11,7 +13,11 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
- //
+ $this->app->singleton(CacheKeyBuilder::class, function () {
+ return new CacheKeyBuilder(version: 'v1');
+ });
+
+ $this->app->singleton(CacheInvalidator::class);
}
/**
diff --git a/app/Services/AutoReportService.php b/app/Services/AutoReportService.php
index ed229f5..0ef475a 100644
--- a/app/Services/AutoReportService.php
+++ b/app/Services/AutoReportService.php
@@ -70,7 +70,7 @@ class AutoReportService
// Проверяем, существует ли уже отчет на эту дату
$existingReport = Report::where('rf_department_id', $department->department_id)
- ->whereDate('sent_at', $dateRange->endSql())
+ ->exactPeriod($dateRange->startSql(), $dateRange->endSql())
->first();
if ($existingReport && !$force) {
diff --git a/app/Services/BedDayService.php b/app/Services/BedDayService.php
index a9a00a0..ed44364 100644
--- a/app/Services/BedDayService.php
+++ b/app/Services/BedDayService.php
@@ -41,9 +41,8 @@ class BedDayService
// Находим отчеты за период
$reports = Report::where('rf_department_id', $departmentId)
-// ->whereBetween('created_at', [$actualStartDate, $endDate])
- ->where('sent_at', '>=', $actualStartDate)
- ->where('sent_at', '<=', $endDate)
+ ->where('period_start', '>=', $actualStartDate)
+ ->where('period_end', '<', $endDate)
->pluck('report_id');
if ($reports->isEmpty()) {
@@ -109,9 +108,8 @@ class BedDayService
// Находим все отчеты за период по отделениям
$reportsByDepartment = Report::whereIn('rf_department_id', $departmentIds)
-// ->whereBetween('created_at', [$actualStartDate, $endDate])
- ->where('sent_at', '>=', $actualStartDate)
- ->where('sent_at', '<=', $endDate)
+ ->where('period_start', '>=', $actualStartDate)
+ ->where('period_end', '<', $endDate)
->select('report_id', 'rf_department_id')
->get()
->groupBy('rf_department_id');
@@ -190,9 +188,8 @@ class BedDayService
public function getDetailedStatsFromSnapshots(int $departmentId, string $startDate, string $endDate): array
{
$reports = Report::where('rf_department_id', $departmentId)
-// ->whereBetween('created_at', [$startDate, $endDate])
- ->where('sent_at', '>', $startDate)
- ->where('sent_at', '<=', $endDate)
+ ->where('period_start', '>=', $startDate)
+ ->where('period_end', '<', $endDate)
->pluck('report_id');
if ($reports->isEmpty()) {
@@ -296,7 +293,7 @@ class BedDayService
foreach ($reports as $report) {
// Для каждого отчета считаем средний койко-день за последние 30 дней до даты отчета
- $endDate = $report->created_at;
+ $endDate = $report->period_end;
$startDate = Carbon::startOfYear();
$avg = $this->getAverageBedDaysFromSnapshots(
diff --git a/app/Services/CurrentPatientService.php b/app/Services/CurrentPatientService.php
new file mode 100644
index 0000000..86cfdbf
--- /dev/null
+++ b/app/Services/CurrentPatientService.php
@@ -0,0 +1,81 @@
+getHistoricalCurrentMedicalHistoryIds($type, $branchId, $dateRange);
+ }
+
+ $typeIds = match($type) {
+ 'plan' => self::PLAN_STATUSES,
+ 'emergency' => self::EMERGENCY_STATUSES,
+ default => null
+ };
+
+ return MisMigrationPatient::currentlyInTreatment($branchId)
+ ->whereHas('medicalHistory', function ($q) use ($typeIds) {
+ $q->whereIn('rf_EmerSignID', $typeIds);
+ })
+ ->pluck('rf_MedicalHistoryID')
+ ->toArray();
+ }
+
+ public function getHistoricalCurrentMedicalHistoryIds(?string $type, int $branchId, DateRange $dateRange): array
+ {
+ $endAt = $dateRange->endSql();
+
+ $typeIds = match($type) {
+ 'plan' => self::PLAN_STATUSES,
+ 'emergency' => self::EMERGENCY_STATUSES,
+ default => null
+ };
+
+ $lastMovementSubquery = DB::table('stt_migrationpatient as mp')
+ ->selectRaw('
+ mp."rf_MedicalHistoryID",
+ mp."rf_StationarBranchID",
+ mp."DateIngoing",
+ mp."SystemDate",
+ mp."MigrationPatientID",
+ row_number() over (
+ partition by mp."rf_MedicalHistoryID"
+ order by mp."DateIngoing" desc, mp."SystemDate" desc, mp."MigrationPatientID" desc
+ ) as rn
+ ')
+ ->where('mp.rf_MedicalHistoryID', '<>', 0)
+ ->where('mp.DateIngoing', '<', $endAt);
+
+ return DB::query()
+ ->fromSub($lastMovementSubquery, 'last_mp')
+ ->leftJoin('stt_medicalhistory as mh', 'mh.MedicalHistoryID', '=', 'last_mp.rf_MedicalHistoryID')
+ ->where('last_mp.rn', 1)
+ ->where('last_mp.rf_StationarBranchID', $branchId)
+ ->where(function ($q) use ($endAt) {
+ $q->whereNull('mh.DateExtract')
+ ->orWhere('mh.DateExtract', '>=', $endAt)
+ ->orWhereDate('mh.DateExtract', '1900-01-01')
+ ->orWhereDate('mh.DateExtract', '2222-01-01');
+ })
+ ->where(function ($q) use ($endAt) {
+ $q->whereNull('mh.DateDeath')
+ ->orWhere('mh.DateDeath', '>=', $endAt)
+ ->orWhereDate('mh.DateDeath', '1900-01-01')
+ ->orWhereDate('mh.DateDeath', '2222-01-01');
+ })
+ ->when($typeIds, function ($q) use ($typeIds) {
+ $q->whereIn('mh.rf_EmerSignID', $typeIds);
+ })
+ ->pluck('last_mp.rf_MedicalHistoryID')
+ ->toArray();
+ }
+}
diff --git a/app/Services/DateRangeService.php b/app/Services/DateRangeService.php
index 3263803..66369e6 100644
--- a/app/Services/DateRangeService.php
+++ b/app/Services/DateRangeService.php
@@ -39,6 +39,34 @@ class DateRangeService
);
}
+ /**
+ * Получить диапазон дат для страницы /report:
+ * всегда сутки (07:00 предыдущего дня -> 07:00 выбранного дня)
+ */
+ public function getReportDateRangeFromRequest(Request $request, User $user): DateRange
+ {
+ $endAtInput = $request->query('endAt', $request->get('endAt'));
+ $startAtInput = $request->query('startAt', $request->get('startAt'));
+ $endDate = $endAtInput
+ ? $this->parseDate($endAtInput)
+ : Carbon::now('Asia/Yakutsk');
+
+ $startDate = $startAtInput
+ ? $this->parseDate($startAtInput)
+ : Carbon::now('Asia/Yakutsk');
+
+ $endDate = $endDate->copy()->setTime(7, 0);
+ $startDate = $startDate->copy()->subDay()->setTime(7, 0);
+
+ return new DateRange(
+ startDate: $startDate,
+ endDate: $endDate,
+ startDateRaw: $startDate->format('Y-m-d H:i:s'),
+ endDateRaw: $endDate->format('Y-m-d H:i:s'),
+ isOneDay: true
+ );
+ }
+
/**
* Получить диапазон дат для статистики (по умолчанию с начала года)
*/
@@ -56,6 +84,7 @@ class DateRangeService
// По умолчанию: с начала года до сегодня
$startDate = Carbon::now('Asia/Yakutsk')
->startOfYear() // 1 января текущего года
+ ->subDay()
->setTime(7, 0);
$endDate = Carbon::now('Asia/Yakutsk')
diff --git a/app/Services/MetricCalculators/AverageBedDaysCalculator.php b/app/Services/MetricCalculators/AverageBedDaysCalculator.php
index 209cd57..5066243 100644
--- a/app/Services/MetricCalculators/AverageBedDaysCalculator.php
+++ b/app/Services/MetricCalculators/AverageBedDaysCalculator.php
@@ -24,9 +24,8 @@ class AverageBedDaysCalculator extends BaseMetricService implements MetricCalcul
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $departmentIds)
->where('mr.rf_metrika_item_id', 18)
-// ->whereBetween('r.created_at', [$startDate, $endDate])
- ->where('r.sent_at', '>', $startDate)
- ->where('r.sent_at', '<=', $endDate)
+ ->where('r.period_start', '>=', $startDate)
+ ->where('r.period_end', '<', $endDate)
->select(
'r.rf_department_id',
DB::raw('AVG(CAST(mr.value AS DECIMAL)) as avg_value')
diff --git a/app/Services/MetricCalculators/LethalityCalculator.php b/app/Services/MetricCalculators/LethalityCalculator.php
index f8f9a25..146e546 100644
--- a/app/Services/MetricCalculators/LethalityCalculator.php
+++ b/app/Services/MetricCalculators/LethalityCalculator.php
@@ -24,9 +24,8 @@ class LethalityCalculator extends BaseMetricService implements MetricCalculatorI
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $departmentIds)
->whereIn('mr.rf_metrika_item_id', [7, 9])
-// ->whereBetween('r.created_at', [$startDate, $endDate])
- ->where('r.sent_at', '>', $startDate)
- ->where('r.sent_at', '<=', $endDate)
+ ->where('r.period_start', '>=', $startDate)
+ ->where('r.period_end', '<', $endDate)
->select(
'r.rf_department_id',
'mr.rf_metrika_item_id',
diff --git a/app/Services/MetricCalculators/PreoperativeDaysCalculator.php b/app/Services/MetricCalculators/PreoperativeDaysCalculator.php
index 454c6d5..014c20c 100644
--- a/app/Services/MetricCalculators/PreoperativeDaysCalculator.php
+++ b/app/Services/MetricCalculators/PreoperativeDaysCalculator.php
@@ -25,9 +25,8 @@ class PreoperativeDaysCalculator extends BaseMetricService implements MetricCalc
// Получаем отчеты за период
$reports = Report::whereIn('rf_department_id', $departmentIds)
- ->where('sent_at', '>', $startDate)
- ->where('sent_at', '<=', $endDate)
-// ->whereBetween('created_at', [$startDate, $endDate])
+ ->where('period_start', '>=', $startDate)
+ ->where('period_end', '<', $endDate)
->get(['report_id', 'rf_department_id'])
->keyBy('report_id');
diff --git a/app/Services/MetrikaService.php b/app/Services/MetrikaService.php
index 157992b..fd07623 100644
--- a/app/Services/MetrikaService.php
+++ b/app/Services/MetrikaService.php
@@ -28,8 +28,8 @@ class MetrikaService
->join('stt_migrationpatient as mp', 'mhs.rf_medicalhistory_id', '=', 'mp.rf_MedicalHistoryID')
->join('stt_surgicaloperation as so', 'mhs.rf_medicalhistory_id', '=', 'so.rf_MedicalHistoryID')
->whereIn('r.rf_department_id', $departmentIds)
- ->whereDate('r.sent_at', '>=', $startDate)
- ->whereDate('r.sent_at', '<=', $endDate)
+ ->where('r.period_start', '>=', $startDate)
+ ->where('r.period_end', '<', $endDate)
->whereIn('mhs.patient_type', ['discharged', 'deceased'])
->select(
'r.rf_department_id',
diff --git a/app/Services/PatientService.php b/app/Services/PatientService.php
index 617393b..d4c246d 100644
--- a/app/Services/PatientService.php
+++ b/app/Services/PatientService.php
@@ -7,11 +7,16 @@ use App\Models\MisMigrationPatient;
use App\Models\MisReanimation;
use App\Models\MisSurgicalOperation;
use App\Models\ObservationPatient;
-use Carbon\Carbon;
-use Illuminate\Support\Facades\DB;
class PatientService
{
+ public function __construct(
+ protected OutcomePatientService $outcomePatientService,
+ protected RecipientPatientService $recipientPatientService,
+ protected CurrentPatientService $currentPatientService
+ )
+ { }
+
/**
* Получить плановых или экстренных пациентов
*/
@@ -25,36 +30,83 @@ class PatientService
bool $includeCurrent = false,
bool $fillableAuto = false
) {
- // Получаем поступивших сегодня
- $recipientQuery = $this->buildRecipientQuery($type, $isHeadOrAdmin, $branchId, $dateRange, $fillableAuto);
- if ($fillableAuto)
- $recipientIds = $recipientQuery->distinct()->pluck('rf_MedicalHistoryID')->toArray();
- else
- $recipientIds = $recipientQuery->pluck('rf_MedicalHistoryID')->toArray();
-
- // Если нужно добавить уже находящихся в отделении
- if ($includeCurrent) {
- if ($fillableAuto) {
- $currentIds = $this->getHistoricalCurrentMedicalHistoryIds($branchId, $dateRange);
- } else {
- $currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
- ->pluck('rf_MedicalHistoryID')
- ->toArray();
- }
-
- $medicalHistoryIds = array_unique(array_merge($recipientIds, $currentIds));
- } else {
- $medicalHistoryIds = $recipientIds;
- }
+ $medicalHistoryIds = $this->recipientPatientService->resolvePlanOrEmergencyMedicalHistoryIds(
+ $type,
+ $isHeadOrAdmin,
+ $branchId,
+ $dateRange,
+ $includeCurrent,
+ $fillableAuto
+ );
if (empty($medicalHistoryIds)) {
+ return $countOnly ? 0 : collect();
+ }
+
+ if ($countOnly) {
+ return count($medicalHistoryIds);
+ }
+
+ if ($onlyIds) {
+ return collect($medicalHistoryIds);
+ }
+
+ $recipientIds = $this->recipientPatientService->getRecipientMedicalHistoryIds(
+ $type,
+ $isHeadOrAdmin,
+ $branchId,
+ $dateRange
+ );
+
+ $res = $this->buildPatientCardsQuery($medicalHistoryIds, $branchId);
+
+ return $res->get()
+ ->map(function ($patient) use ($recipientIds) {
+ $patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds, true);
+ return $patient;
+ });
+
+ }
+
+ /**
+ * Получить всех пациентов в отделении (поступившие сегодня + уже лечащиеся)
+ */
+ public function getAllPatientsInDepartment(
+ bool $isHeadOrAdmin,
+ int $branchId,
+ DateRange $dateRange,
+ bool $countOnly = false,
+ bool $onlyIds = false,
+ bool $fillableAuto = false
+ ) {
+ $recipientIds = $this->recipientPatientService->buildRecipientQuery(null, $isHeadOrAdmin, $branchId, $dateRange, $fillableAuto)
+ ->pluck('rf_MedicalHistoryID')
+ ->toArray();
+
+ if ($fillableAuto) {
+ $currentIds = $this->currentPatientService->getHistoricalCurrentMedicalHistoryIds(null, $branchId, $dateRange);
+ } else {
+ $currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
+ ->pluck('rf_MedicalHistoryID')
+ ->toArray();
+ }
+
+ $allIds = array_unique(array_merge($recipientIds, $currentIds));
+
+ if (empty($allIds)) {
if ($countOnly) return 0;
- if ($onlyIds) return collect();
return collect();
}
- // Получаем истории
- $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
+ if ($countOnly) {
+ return count($allIds);
+ }
+
+ if ($onlyIds) {
+ return collect($allIds);
+ }
+
+ $res = MisMedicalHistory::whereIn('MedicalHistoryID', $allIds)
->select([
'MedicalHistoryID',
'FAMILY',
@@ -83,10 +135,10 @@ class PatientService
},
'outcomeMigration' => function ($q) {
$q->select([
- 'MigrationPatientID',
- 'rf_MedicalHistoryID',
- 'DateOut',
- 'rf_DiagnosID',
+ 'stt_migrationpatient.MigrationPatientID',
+ 'stt_migrationpatient.rf_MedicalHistoryID',
+ 'stt_migrationpatient.DateOut',
+ 'stt_migrationpatient.rf_DiagnosID',
])->with(['mainDiagnosis' => function ($diagnosisQuery) {
$diagnosisQuery->select([
'DiagnosID',
@@ -127,145 +179,10 @@ class PatientService
])
->orderBy('DateRecipient', 'DESC');
- // Фильтруем по типу (план/экстренные)
- if ($type === 'plan') {
- $query->plan();
- } elseif ($type === 'emergency') {
- $query->emergency();
- }
-
- if ($countOnly) {
- return $query->count();
- }
-
- if ($onlyIds) {
- return $query->pluck('MedicalHistoryID');
- }
-
- return $query->get()->map(function ($patient) use ($recipientIds) {
- // Добавляем флаг "поступил сегодня"
- $patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds);
- return $patient;
- });
- }
-
- /**
- * Получить всех пациентов в отделении (поступившие сегодня + уже лечащиеся)
- */
- public function getAllPatientsInDepartment(
- bool $isHeadOrAdmin,
- int $branchId,
- DateRange $dateRange,
- bool $countOnly = false,
- bool $onlyIds = false,
- bool $fillableAuto = false
- ) {
- // Поступившие сегодня
- $recipientIds = $this->buildRecipientQuery(null, $isHeadOrAdmin, $branchId, $dateRange, $fillableAuto)
- ->pluck('rf_MedicalHistoryID')
- ->toArray();
-
- // Уже находящиеся на лечении
- if ($fillableAuto) {
- $currentIds = $this->getHistoricalCurrentMedicalHistoryIds($branchId, $dateRange);
- } else {
- $currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
- ->pluck('rf_MedicalHistoryID')
- ->toArray();
- }
-
- // Объединяем и убираем дубли
- $allIds = array_unique(array_merge($recipientIds, $currentIds));
-
- if (empty($allIds)) {
- if ($countOnly) return 0;
- return collect();
- }
-
- if ($countOnly) {
- return count($allIds);
- }
-
- if ($onlyIds) {
- return collect($allIds);
- }
-
- return MisMedicalHistory::whereIn('MedicalHistoryID', $allIds)
- ->select([
- 'MedicalHistoryID',
- 'FAMILY',
- 'Name',
- 'OT',
- 'BD',
- 'DateRecipient',
- 'DateExtract',
- 'rf_EmerSignID',
- 'rf_kl_VisitResultID',
- ])
- ->with([
- 'surgicalOperations' => function ($q) {
- $q->select([
- 'SurgicalOperationID',
- 'rf_MedicalHistoryID',
- 'rf_kl_ServiceMedicalID',
- 'Date',
- ])->with(['serviceMedical' => function ($serviceQuery) {
- $serviceQuery->select([
- 'ServiceMedicalID',
- 'ServiceMedicalCode',
- 'ServiceMedicalName',
- ]);
- }]);
- },
- 'outcomeMigration' => function ($q) {
- $q->select([
- 'MigrationPatientID',
- 'rf_MedicalHistoryID',
- 'DateOut',
- 'rf_DiagnosID',
- ])->with(['mainDiagnosis' => function ($diagnosisQuery) {
- $diagnosisQuery->select([
- 'DiagnosID',
- 'rf_MKBID',
- ])->with(['mkb' => function ($mkbQuery) {
- $mkbQuery->select([
- 'MKBID',
- 'DS',
- 'NAME',
- ]);
- }]);
- }]);
- },
- 'migrations' => function ($q) use ($branchId) {
- $q->where('rf_StationarBranchID', $branchId)
- ->select([
- 'MigrationPatientID',
- 'rf_MedicalHistoryID',
- 'rf_DiagnosID',
- 'DateIngoing',
- 'rf_StationarBranchID',
- ])
- ->orderByDesc('DateIngoing')
- ->with(['mainDiagnosis' => function ($diagnosisQuery) {
- $diagnosisQuery->select([
- 'DiagnosID',
- 'rf_MKBID',
- 'rf_MigrationPatientID',
- ])->with(['mkb' => function ($mkbQuery) {
- $mkbQuery->select([
- 'MKBID',
- 'DS',
- 'NAME',
- ]);
- }]);
- }]);
- },
- ])
- ->orderBy('DateRecipient', 'DESC')
- ->get()
- ->map(function ($patient) use ($recipientIds) {
- $patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds);
- return $patient;
+ return $res->get()
+ ->map(function ($patient) use ($recipientIds) {
+ $patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds);
+ return $patient;
});
}
@@ -288,9 +205,8 @@ class PatientService
if ($onlyIds) $patients = $query->pluck('MedicalHistoryID');
else {
- // Загрузка отношений, необходимых для FormattedPatientResource
$query->with([
- 'outcomeMigration.mainDiagnosis.mkb', // mkb через диагноз
+ 'outcomeMigration.mainDiagnosis.mkb',
]);
$patients = $query->get();
}
@@ -313,73 +229,7 @@ class PatientService
string $outcomeType = 'all',
bool $onlyIds = false
) {
- $query = MisMedicalHistory::query()
- ->where('MedicalHistoryID', '<>', 0)
- ->whereHas('migrations', function ($migrationQuery) use ($branchId, $outcomeType) {
- $migrationQuery->where('rf_StationarBranchID', $branchId);
-
- if ($outcomeType === 'deceased') {
- $migrationQuery->whereIn('rf_kl_VisitResultID', [5, 6, 15, 16]);
- } elseif ($outcomeType === 'transferred') {
- $migrationQuery->whereIn('rf_kl_VisitResultID', [4, 14]);
- } elseif ($outcomeType === 'discharged') {
- $migrationQuery->whereIn('rf_kl_VisitResultID', [1, 11, 2, 12, 7, 18, 48]);
- } elseif ($outcomeType === 'without-transferred') {
- $migrationQuery->whereNotIn('rf_kl_VisitResultID', [4, 14])
- ->where('rf_kl_VisitResultID', '<>', 0);
- }
- });
-
- if ($dateRange->isOneDay) {
- $query->where('DateExtract', '>', $dateRange->startSql())
- ->where('DateExtract', '<=', $dateRange->endSql());
- } else {
- $startAt = $dateRange->startSql();
- $endDate = $dateRange->end()->toDateString();
- $query->where('DateExtract', '>', $startAt)
- ->whereDate('DateExtract', '<=', $endDate);
- }
-
- if ($onlyIds) {
- return $query->pluck('MedicalHistoryID');
- }
-
- return $query
- ->select([
- 'MedicalHistoryID',
- 'FAMILY',
- 'Name',
- 'OT',
- 'BD',
- 'DateRecipient',
- 'DateExtract',
- 'rf_EmerSignID',
- 'rf_kl_VisitResultID',
- ])
- ->with([
- 'outcomeMigration' => function ($q) {
- $q->select([
- 'MigrationPatientID',
- 'rf_MedicalHistoryID',
- 'DateOut',
- 'rf_DiagnosID',
- ])->with(['mainDiagnosis' => function ($diagnosisQuery) {
- $diagnosisQuery->select([
- 'DiagnosID',
- 'rf_MKBID',
- ])->with(['mkb' => function ($mkbQuery) {
- $mkbQuery->select([
- 'MKBID',
- 'DS',
- 'NAME',
- ]);
- }]);
- }]);
- },
- ])
- ->orderBy('DateRecipient', 'DESC')
- ->get()
- ->map(fn ($patient) => $this->addOutcomeInfo($patient));
+ return $this->outcomePatientService->getOutcomePatients($branchId, $dateRange, $outcomeType, $onlyIds);
}
/**
@@ -443,10 +293,10 @@ class PatientService
},
'outcomeMigration' => function ($q) {
$q->select([
- 'MigrationPatientID',
- 'rf_MedicalHistoryID',
- 'DateOut',
- 'rf_DiagnosID',
+ 'stt_migrationpatient.MigrationPatientID',
+ 'stt_migrationpatient.rf_MedicalHistoryID',
+ 'stt_migrationpatient.DateOut',
+ 'stt_migrationpatient.rf_DiagnosID',
])->with(['mainDiagnosis' => function ($diagnosisQuery) {
$diagnosisQuery->select([
'DiagnosID',
@@ -478,7 +328,6 @@ class PatientService
->completed()
->where('Date', '>=', $dateRange->startSql())
->where('Date', '<=', $dateRange->endSql());
-// ->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
if ($type === 'plan') {
$query->where('rf_TypeSurgOperationInTimeID', 6);
@@ -493,164 +342,6 @@ class PatientService
return $query->get();
}
- /**
- * Получить текущих пациентов
- */
- public function getCurrentPatients(int $branchId, bool $countOnly = false)
- {
- $medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId)
- ->pluck('rf_MedicalHistoryID')
- ->unique()
- ->toArray();
-
- if (empty($medicalHistoryIds)) {
- return $countOnly ? 0 : collect();
- }
-
- $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
- ->currentlyHospitalized()
- ->with(['surgicalOperations'])
- ->orderBy('DateRecipient', 'DESC');
-
- if ($countOnly) {
- return $query->count();
- }
-
- return $query->get();
- }
-
- /**
- * Собрать базовый запрос для пациентов
- */
- private function buildPatientQuery(
- ?string $type,
- bool $isHeadOrAdmin,
- int $branchId,
- DateRange $dateRange
- ) {
- if ($isHeadOrAdmin) {
- $query = MisMigrationPatient::whereInDepartment($branchId)
- ->where('DateIngoing', '>=', $dateRange->startSql())
- ->where('DateIngoing', '<=', $dateRange->endSql());
-// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
- } else {
- $query = MisMigrationPatient::currentlyInTreatment($branchId)
- ->where('DateIngoing', '>=', $dateRange->startSql())
- ->where('DateIngoing', '<=', $dateRange->endSql());
-// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
- }
-
- $medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->toArray();
-
- if (empty($medicalHistoryIds)) {
- return MisMedicalHistory::whereRaw('1 = 0');
- }
-
- $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds);
-
- if ($type === 'plan') {
- $query->plan();
- } elseif ($type === 'emergency') {
- $query->emergency();
- }
-
- if (!$isHeadOrAdmin && !in_array($type, ['discharged', 'transferred', 'deceased'])) {
- $query->currentlyHospitalized();
- }
-
- return $query;
- }
-
- /**
- * Построить запрос для поступивших пациентов
- */
- private function buildRecipientQuery(
- ?string $type,
- bool $isHeadOrAdmin,
- int $branchId,
- DateRange $dateRange,
- bool $fillableAuto = false
- ) {
- $startAt = $dateRange->start()->copy()->subDay()->format('Y-m-d H:i:s');
- $endAt = $dateRange->end()->copy()->addDay()->format('Y-m-d H:i:s');
-
- if ($dateRange->isOneDay) {
- $startAt = $dateRange->startSql();
- $endAt = $dateRange->endSql();
- }
-
- $query = DB::table('stt_medicalhistory as mh')
- ->selectRaw('mh."MedicalHistoryID" as "rf_MedicalHistoryID"')
- ->where('mh.MedicalHistoryID', '<>', 0);
-
- $query->whereExists(function ($subQuery) use ($branchId, $startAt, $endAt) {
- $subQuery->select(DB::raw(1))
- ->from('stt_migrationpatient as mp')
- ->whereColumn('mp.rf_MedicalHistoryID', 'mh.MedicalHistoryID')
- ->where('mp.rf_StationarBranchID', $branchId)
- ->where('mp.DateIngoing', '>', $startAt)
- ->where('mp.DateIngoing', '<=', $endAt);
- });
-
- if ($type === 'plan') {
- $query->where('mh.rf_EmerSignID', 1);
- } elseif ($type === 'emergency') {
- $query->whereIn('mh.rf_EmerSignID', [2, 4]);
- }
-
- return $query->distinct();
- }
-
- private function getHistoricalCurrentMedicalHistoryIds(int $branchId, DateRange $dateRange): array
- {
- // Исторический срез по основной таблице миграций:
- // для каждой истории болезни берём последнюю миграцию в отделении
- // на момент конца периода и проверяем, что пациент числился в отделении.
- $latestRows = DB::table('stt_migrationpatient')
- ->select('rf_MedicalHistoryID', DB::raw('MAX("MigrationPatientID") as max_migration_patient_id'))
- ->where('rf_StationarBranchID', $branchId)
- ->where('rf_MedicalHistoryID', '<>', 0)
- ->where('DateIngoing', '<=', $dateRange->endSql())
- ->groupBy('rf_MedicalHistoryID');
-
- return DB::table('stt_migrationpatient as mp')
- ->joinSub($latestRows, 'latest', function ($join) {
- $join->on('mp.rf_MedicalHistoryID', '=', 'latest.rf_MedicalHistoryID')
- ->on('mp.MigrationPatientID', '=', 'latest.max_migration_patient_id');
- })
- ->join('stt_medicalhistory as mh', 'mh.MedicalHistoryID', '=', 'mp.rf_MedicalHistoryID')
- ->where('mp.rf_StationarBranchID', $branchId)
- ->where('mh.DateRecipient', '<=', $dateRange->endSql())
- ->where('mp.DateOut', '>=', $dateRange->endSql())
- ->where(function ($query) use ($dateRange) {
- $query->where('mh.DateExtract', '>', $dateRange->endSql())
- ->orWhereDate('mh.DateExtract', '1900-01-01');
- })
- ->distinct()
- ->pluck('mp.rf_MedicalHistoryID')
- ->toArray();
- }
-
- /**
- * Добавить информацию об исходе пациенту
- */
- private function addOutcomeInfo(MisMedicalHistory $patient)
- {
- $latestMigration = $patient->migrations
- ->whereNotNull('DateOut')
- ->where('DateOut', '<>', '1900-01-01')
- ->sortByDesc('DateOut')
- ->first();
-
- if ($latestMigration) {
- $patient->outcome_type = $this->getOutcomeTypeName($latestMigration->rf_kl_VisitResultID);
- $patient->outcome_date = $latestMigration->DateOut;
- $patient->visit_result_id = $latestMigration->rf_kl_VisitResultID;
- }
-
- return $patient;
- }
-
/**
* Получить количество пациентов по типу с учетом уже находящихся в отделении
*/
@@ -671,16 +362,81 @@ class PatientService
);
}
- /**
- * Получить название типа исхода
- */
- private function getOutcomeTypeName(int $visitResultId): string
+ private function buildPatientCardsQuery(array $medicalHistoryIds, int $branchId)
{
- return match($visitResultId) {
- 1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка',
- 2, 3, 4, 12, 13, 14 => 'Перевод',
- 5, 6, 15, 16 => 'Умер',
- default => 'Другое (' . $visitResultId . ')'
- };
+ return MisMedicalHistory::query()
+ ->whereIn('MedicalHistoryID', $medicalHistoryIds)
+ ->select([
+ 'MedicalHistoryID',
+ 'FAMILY',
+ 'Name',
+ 'OT',
+ 'BD',
+ 'DateRecipient',
+ 'DateExtract',
+ 'DateDeath',
+ 'rf_EmerSignID',
+ 'rf_kl_VisitResultID',
+ ])
+ ->with([
+ 'surgicalOperations' => function ($q) {
+ $q->select([
+ 'SurgicalOperationID',
+ 'rf_MedicalHistoryID',
+ 'rf_kl_ServiceMedicalID',
+ 'Date',
+ ])->with(['serviceMedical' => function ($serviceQuery) {
+ $serviceQuery->select([
+ 'ServiceMedicalID',
+ 'ServiceMedicalCode',
+ 'ServiceMedicalName',
+ ]);
+ }]);
+ },
+ 'outcomeMigration' => function ($q) {
+ $q->select([
+ 'stt_migrationpatient.MigrationPatientID',
+ 'stt_migrationpatient.rf_MedicalHistoryID',
+ 'stt_migrationpatient.DateOut',
+ 'stt_migrationpatient.rf_DiagnosID',
+ ])->with(['mainDiagnosis' => function ($diagnosisQuery) {
+ $diagnosisQuery->select([
+ 'DiagnosID',
+ 'rf_MKBID',
+ ])->with(['mkb' => function ($mkbQuery) {
+ $mkbQuery->select([
+ 'MKBID',
+ 'DS',
+ 'NAME',
+ ]);
+ }]);
+ }]);
+ },
+ 'migrations' => function ($q) use ($branchId) {
+ $q->where('rf_StationarBranchID', $branchId)
+ ->select([
+ 'MigrationPatientID',
+ 'rf_MedicalHistoryID',
+ 'rf_DiagnosID',
+ 'DateIngoing',
+ 'rf_StationarBranchID',
+ ])
+ ->orderByDesc('DateIngoing')
+ ->with(['mainDiagnosis' => function ($diagnosisQuery) {
+ $diagnosisQuery->select([
+ 'DiagnosID',
+ 'rf_MKBID',
+ 'rf_MigrationPatientID',
+ ])->with(['mkb' => function ($mkbQuery) {
+ $mkbQuery->select([
+ 'MKBID',
+ 'DS',
+ 'NAME',
+ ]);
+ }]);
+ }]);
+ },
+ ])
+ ->orderBy('DateRecipient', 'DESC');
}
}
diff --git a/app/Services/RecipientPatientService.php b/app/Services/RecipientPatientService.php
new file mode 100644
index 0000000..12cc653
--- /dev/null
+++ b/app/Services/RecipientPatientService.php
@@ -0,0 +1,101 @@
+buildRecipientQuery($type, $isHeadOrAdmin, $branchId, $dateRange, false)
+ ->distinct()
+ ->pluck('rf_MedicalHistoryID')
+ ->toArray();
+ }
+
+ /**
+ * Построить запрос для поступивших пациентов
+ */
+ public function buildRecipientQuery(
+ ?string $type,
+ bool $isHeadOrAdmin,
+ int $branchId,
+ DateRange $dateRange,
+ bool $fillableAuto = false
+ ) {
+ $startAt = $dateRange->start()->copy()->format('Y-m-d H:i:s');
+ $endAt = $dateRange->end()->copy()->format('Y-m-d H:i:s');
+
+ if ($dateRange->isOneDay) {
+ $startAt = $dateRange->startSql();
+ $endAt = $dateRange->endSql();
+ }
+
+ $query = DB::table('stt_medicalhistory as mh')
+ ->selectRaw('mh."MedicalHistoryID" as "rf_MedicalHistoryID"')
+ ->where('mh.MedicalHistoryID', '<>', 0);
+
+ $query->whereExists(function ($subQuery) use ($branchId, $startAt, $endAt) {
+ $subQuery->select(DB::raw(1))
+ ->from('stt_migrationpatient as mp')
+ ->whereColumn('mp.rf_MedicalHistoryID', 'mh.MedicalHistoryID')
+ ->where('mp.rf_StationarBranchID', $branchId)
+ ->where('mp.DateIngoing', '>', $startAt)
+ ->where('mp.DateIngoing', '<=', $endAt);
+ });
+
+ if ($type === 'plan') {
+ $query->where('mh.rf_EmerSignID', 1);
+ } elseif ($type === 'emergency') {
+ $query->whereIn('mh.rf_EmerSignID', [2, 4]);
+ }
+
+ return $query->distinct();
+ }
+
+ // Получить IDs состоящих
+ private function getCurrentMedicalHistoryIds(
+ string $type,
+ int $branchId,
+ DateRange $dateRange,
+ bool $fillableAuto,
+ ): array {
+ return $this->currentPatientService->getCurrentMedicalHistoryIds($type, $branchId, $dateRange, $fillableAuto);
+ }
+
+ public function resolvePlanOrEmergencyMedicalHistoryIds(
+ ?string $type,
+ bool $isHeadOrAdmin,
+ int $branchId,
+ DateRange $dateRange,
+ bool $includeCurrent,
+ bool $fillableAuto
+ ): array {
+ $recipientIds = $this->getRecipientMedicalHistoryIds(
+ $type,
+ $isHeadOrAdmin,
+ $branchId,
+ $dateRange
+ );
+
+ if (!$includeCurrent) {
+ return array_values(array_unique($recipientIds));
+ }
+
+ $currentIds = $this->getCurrentMedicalHistoryIds($type, $branchId, $dateRange, $fillableAuto);
+
+ return array_values(array_unique(array_merge($recipientIds, $currentIds)));
+ }
+}
diff --git a/app/Services/ReportService.php b/app/Services/ReportService.php
index e6e6489..5dfe7fd 100644
--- a/app/Services/ReportService.php
+++ b/app/Services/ReportService.php
@@ -126,35 +126,79 @@ class ReportService
*/
public function storeReport(array $data, User $user, $fillableAuto = false): Report
{
- $report = DB::transaction(function () use ($data, $user, $fillableAuto) {
- $report = $this->createOrUpdateReport($data, $user);
+ $this->prepareMemoryForHeavySave();
- // Сохраняем все, что НЕ зависит от других отчетов
- $this->saveMetrics($report, $data['metrics'] ?? []);
- $this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []);
- $this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id);
- $this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
- $this->syncCalculatedMetrics($report, $user, $data);
+ try {
+ $report = DB::transaction(function () use ($data, $user, $fillableAuto) {
+ $report = $this->createOrUpdateReport($data, $user);
- return $report;
- });
+ // Сохраняем все, что НЕ зависит от других отчетов
+ $this->saveMetrics($report, $data['metrics'] ?? []);
- DB::transaction(function () use ($report) {
- // Сохраняем метрику среднего койко-дня из снапшотов
- $this->saveAverageBedDaysMetricFromSnapshots($report);
+ $this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []);
- $this->saveLethalMetricFromSnapshots($report);
+ $this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id);
- $this->savePreoperativeMetric($report);
+ $shouldBuildSnapshots = (bool) $fillableAuto;
+ if ($shouldBuildSnapshots) {
+ $this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
- $this->saveDepartmentLoadedMetric($report);
- });
+ $this->syncCalculatedMetrics($report, $user, $data);
+ } else {
+ MedicalHistorySnapshot::query()
+ ->where('rf_report_id', $report->report_id)
+ ->delete();
+ }
+
+ return $report;
+ });
+
+ $shouldBuildDerivedMetrics = (bool) $fillableAuto;
+ if ($shouldBuildDerivedMetrics) {
+ DB::transaction(function () use ($report) {
+ // Сохраняем метрику койко-дня + среднего койко-дня из снапшотов
+ $this->saveBedDaysMetric($report);
+
+ $this->saveLethalMetricFromSnapshots($report);
+
+ $this->savePreoperativeMetric($report);
+
+ $this->saveDepartmentLoadedMetric($report);
+ });
+ }
+ } catch (\Throwable $e) {
+ throw $e;
+ }
$this->clearCacheAfterReportCreation($user, $report);
return $report;
}
+ private function prepareMemoryForHeavySave(): void
+ {
+ $connectionNames = array_unique(array_filter([
+ DB::getDefaultConnection(),
+ (new MisMedicalHistory)->getConnectionName(),
+ (new MisMigrationPatient)->getConnectionName(),
+ (new MisStationarBranch)->getConnectionName(),
+ ]));
+
+ foreach ($connectionNames as $connectionName) {
+ try {
+ $connection = DB::connection($connectionName);
+ $connection->disableQueryLog();
+ $connection->flushQueryLog();
+ } catch (\Throwable) {
+ // best-effort cleanup only
+ }
+ }
+
+ if (function_exists('gc_collect_cycles')) {
+ gc_collect_cycles();
+ }
+ }
+
public function buildAutoFillReportPayload(User $user, Department $department, DateRange $dateRange): array
{
$branchId = $this->getBranchId($department->rf_mis_department_id);
@@ -170,6 +214,7 @@ class ReportService
],
'sent_at' => $dateRange->endSql(),
'created_at' => $dateRange->endSql(),
+ 'status' => 'submitted',
'metrics' => [
'metrika_item_4' => $metrics['plan'],
'metrika_item_12' => $metrics['emergency'],
@@ -301,73 +346,170 @@ class ReportService
/**
* Сохранить метрику койко-дня из снапшотов отчета
*/
- protected function saveAverageBedDaysMetricFromSnapshots(Report $report): void
+ protected function saveBedDaysMetric(Report $report): void
{
try {
- // Получаем все снапшоты выписанных пациентов из этого отчета
- $snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
- ->whereIn('patient_type', ['discharged', 'deceased']) // выписанные и умершие
- ->with('medicalHistory')
- ->get();
+ $result = $this->calculateBedDaysFromSnapshots($report);
- if ($snapshots->isEmpty()) {
- // Если нет выписанных, сохраняем 0
- MetrikaResult::updateOrCreate(
- [
- 'rf_report_id' => $report->report_id,
- 'rf_metrika_item_id' => 18,
- ],
- ['value' => 0]
- );
-
- \Log::info("No discharged patients in report {$report->report_id}, saved 0");
- return;
- }
-
- // Рассчитываем средний койко-день по снапшотам
- $totalDays = 0;
- $validCount = 0;
-
- foreach ($snapshots as $snapshot) {
- $history = $snapshot->medicalHistory;
-
- if ($history && $history->DateRecipient && $history->DateExtract) {
- // Проверяем что дата выписки не специальная
- if ($history->DateExtract->format('Y-m-d') === '2222-01-01') {
- continue; // пропускаем текущих пациентов
- }
-
- $start = Carbon::parse($history->DateRecipient);
- $end = Carbon::parse($history->DateExtract);
-
- // Проверяем что дата выписки позже даты поступления
- if ($end->gt($start)) {
- $days = $start->diffInDays($end);
- $totalDays += $days;
- $validCount++;
- }
- }
- }
-
- $bedDays = $validCount > 0 ? $totalDays: 0;
-
- // Сохраняем метрику
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
- 'rf_metrika_item_id' => 18,
+ 'rf_metrika_item_id' => 25, // койко-дни
],
- ['value' => $bedDays]
+ ['value' => $result['total_days']]
);
- //\Log::info("Saved average bed days metric for report {$report->report_id}: {$avgBedDays} (from {$validCount} patients)");
-
- } catch (\Exception $e) {
- \Log::error("Failed to save average bed days metric: " . $e->getMessage());
- // Не прерываем выполнение, если метрика не сохранилась
+ MetrikaResult::updateOrCreate(
+ [
+ 'rf_report_id' => $report->report_id,
+ 'rf_metrika_item_id' => 18, // средний койко-день
+ ],
+ ['value' => $result['avg_days']]
+ );
+ } catch (\Throwable $e) {
+ \Log::error('Failed to save bed days metric: ' . $e->getMessage());
}
}
+ protected function calculateBedDaysFromSnapshots(Report $report): array
+ {
+ $snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
+ ->whereIn('patient_type', ['discharged', 'deceased'])
+ ->with('medicalHistory')
+ ->get();
+
+ $totalDays = 0;
+ $patientCount = 0;
+
+ foreach ($snapshots as $snapshot) {
+ $history = $snapshot->medicalHistory;
+
+ if (!$history) {
+ continue;
+ }
+
+ $start = $history->DateRecipientHS ?? $history->DateRecipient ?? null;
+
+ if (!$start) {
+ continue;
+ }
+
+ $end = null;
+
+ if ($snapshot->patient_type === 'deceased') {
+ if ($history->DateDeath && !in_array($history->DateDeath->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
+ $end = $history->DateDeath;
+ } elseif ($history->DateExtract && !in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
+ $end = $history->DateExtract;
+ }
+ } else {
+ if ($history->DateExtract && !in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
+ $end = $history->DateExtract;
+ }
+ }
+
+ if (!$end) {
+ continue;
+ }
+
+ $start = Carbon::parse($start);
+ $end = Carbon::parse($end);
+
+ if ($end->lt($start)) {
+ continue;
+ }
+
+ // Календарные койко-дни
+ $days = $start->startOfDay()->diffInDays($end->startOfDay());
+
+ $totalDays += $days;
+ $patientCount++;
+ }
+
+ return [
+ 'total_days' => $totalDays,
+ 'patient_count' => $patientCount,
+ 'avg_days' => $patientCount > 0 ? round($totalDays / $patientCount, 2) : 0,
+ ];
+ }
+
+ /**
+ * Рассчитать предоперационные койко-дни по снапшотам отчета
+ *
+ * Возвращает:
+ * - total_days: общее количество предоперационных койко-дней
+ * - patient_count: количество пациентов, вошедших в расчет
+ * - avg_days: средний предоперационный койко-день
+ */
+ protected function calculatePreoperativeDaysFromSnapshots(Report $report): array
+ {
+ $patientIds = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
+ ->whereIn('patient_type', ['discharged', 'deceased'])
+ ->pluck('rf_medicalhistory_id')
+ ->unique()
+ ->values();
+
+ if ($patientIds->isEmpty()) {
+ return [
+ 'total_days' => 0,
+ 'patient_count' => 0,
+ 'avg_days' => 0,
+ ];
+ }
+
+ $rows = DB::table('stt_medicalhistory as mh')
+ ->join('stt_surgicaloperation as so', 'so.rf_MedicalHistoryID', '=', 'mh.MedicalHistoryID')
+ ->whereIn('mh.MedicalHistoryID', $patientIds)
+ ->whereNotNull('so.Date')
+ ->select(
+ 'mh.MedicalHistoryID',
+ DB::raw('MIN(so."Date") as first_operation'),
+ 'mh.DateRecipientHS',
+ 'mh.DateRecipient'
+ )
+ ->groupBy('mh.MedicalHistoryID', 'mh.DateRecipientHS', 'mh.DateRecipient')
+ ->get();
+
+ if ($rows->isEmpty()) {
+ return [
+ 'total_days' => 0,
+ 'patient_count' => 0,
+ 'avg_days' => 0,
+ ];
+ }
+
+ $totalDays = 0;
+ $patientCount = 0;
+
+ foreach ($rows as $row) {
+ $startRaw = $row->DateRecipientHS ?? $row->DateRecipient ?? null;
+ $operationRaw = $row->first_operation ?? null;
+
+ if (!$startRaw || !$operationRaw) {
+ continue;
+ }
+
+ $start = Carbon::parse($startRaw);
+ $operation = Carbon::parse($operationRaw);
+
+ if ($operation->lt($start)) {
+ continue;
+ }
+
+ // Разница календарных дат
+ $days = $start->startOfDay()->diffInDays($operation->startOfDay());
+
+ $totalDays += $days;
+ $patientCount++;
+ }
+
+ return [
+ 'total_days' => $totalDays,
+ 'patient_count' => $patientCount,
+ 'avg_days' => $patientCount > 0 ? round($totalDays / $patientCount, 1) : 0,
+ ];
+ }
+
protected function saveLethalMetricFromSnapshots(Report $report): void
{
// Получаем все снапшоты выписанных пациентов из этого отчета
@@ -396,70 +538,18 @@ class ReportService
*/
protected function savePreoperativeMetric(Report $report): void
{
- // 1. Получаем ВСЕ предыдущие отчеты этого отделения
- $allPreviousReports = Report::where('rf_department_id', $report->rf_department_id)
- ->where('sent_at', '<=', $report->sent_at)
- ->orderBy('sent_at')
- ->pluck('report_id');
+ try {
+ $result = $this->calculatePreoperativeDaysFromSnapshots($report);
- if ($allPreviousReports->isEmpty()) {
- $this->saveMetric($report, 21, 0);
- return;
+ $this->saveMetric($report, 26, $result['total_days']);
+ $this->saveMetric($report, 21, $result['avg_days']);
+ } catch (\Throwable $e) {
+ \Log::error('Failed to save preoperative total metric: ' . $e->getMessage());
}
-
- // 2. Получаем ВСЕХ пациентов из всех отчетов (discharged + deceased)
- $allPatients = MedicalHistorySnapshot::whereIn('rf_report_id', $allPreviousReports)
- ->whereIn('patient_type', ['discharged', 'deceased'])
- ->pluck('rf_medicalhistory_id')
- ->unique();
-
- if ($allPatients->isEmpty()) {
- $this->saveMetric($report, 21, 0);
- return;
- }
-
- // 3. Получаем операции для ВСЕХ пациентов
- $operations = DB::table('stt_surgicaloperation as so')
- ->join('stt_migrationpatient as mp', 'so.rf_MedicalHistoryID', '=', 'mp.rf_MedicalHistoryID')
- ->whereIn('so.rf_MedicalHistoryID', $allPatients)
- ->whereNotNull('so.Date')
- ->whereNotNull('mp.DateIngoing')
- ->select(
- 'so.rf_MedicalHistoryID',
- DB::raw('MIN(so."Date") as first_operation'),
- DB::raw('MIN(mp."DateIngoing") as first_admission')
- )
- ->groupBy('so.rf_MedicalHistoryID')
- ->get();
-
- if ($operations->isEmpty()) {
- $this->saveMetric($report, 21, 0);
- return;
- }
-
- // 4. Считаем общее количество дней и пациентов
- $totalDays = 0;
- $patientCount = 0;
-
- foreach ($operations as $op) {
- $days = Carbon::parse($op->first_admission)
- ->diffInDays(Carbon::parse($op->first_operation));
-
- if ($days >= 0) {
- $totalDays += $days;
- $patientCount++;
- }
- }
-
- // 5. Нарастающий итог = общее количество дней / общее количество пациентов
- $avgDays = $patientCount > 0 ? round($totalDays / $patientCount, 1) : 0;
-
- // 6. Сохраняем метрику
- $this->saveMetric($report, 21, $avgDays);
}
/**
- * Сохранить предоперационный койко-день из снапшотов
+ * Сохранить % загруженности
*/
protected function saveDepartmentLoadedMetric(Report $report): void
{
@@ -523,6 +613,18 @@ class ReportService
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
$branchId = $this->getBranchId($department->rf_mis_department_id);
+ if ($sourceScope === 'special') {
+ return $this->getPatientsFromReplica(
+ $department,
+ $user,
+ $status,
+ $dateRange,
+ $branchId,
+ $onlyIds,
+ $includeCurrentPatients
+ );
+ }
+
if ($baseStatus === 'reanimation') {
return $this->getPatientsFromReplica(
$department,
@@ -562,9 +664,13 @@ class ReportService
string $status,
DateRange $dateRange
): int {
- [$baseStatus] = $this->parseScopedStatus($status);
+ [$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
$branchId = $this->getBranchId($department->rf_mis_department_id);
+ if ($sourceScope === 'special') {
+ return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId);
+ }
+
if ($baseStatus === 'reanimation') {
return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId);
}
@@ -611,21 +717,18 @@ class ReportService
];
foreach ($baseStatuses as $baseStatus) {
- $patients = collect($this->getPatientsByStatus($department, $user, $baseStatus, $dateRange));
-
- $misCount = 0;
- $specialCount = 0;
-
- foreach ($patients as $patient) {
- if ($this->isSpecialScopedPatient($patient)) {
- $specialCount++;
- } else {
- $misCount++;
- }
- }
-
- $counts["mis-{$baseStatus}"] = $misCount;
- $counts["special-{$baseStatus}"] = $specialCount;
+ $counts["mis-{$baseStatus}"] = $this->getPatientsCountByStatus(
+ $department,
+ $user,
+ "mis-{$baseStatus}",
+ $dateRange
+ );
+ $counts["special-{$baseStatus}"] = $this->getPatientsCountByStatus(
+ $department,
+ $user,
+ "special-{$baseStatus}",
+ $dateRange
+ );
}
// Выбывшие = выписанные + умершие (без переведенных)
@@ -649,17 +752,20 @@ class ReportService
*/
private function shouldUseSnapshots(Department $department, User $user, DateRange $dateRange, bool $beforeCreate = false): bool
{
- if (($user->isAdmin() || $user->isHeadOfDepartment()) && !$beforeCreate) {
- return true;
+ if ($beforeCreate) {
+ return false;
}
- // Проверяем, есть ли отчет на сегодня
- $reportToday = Report::whereDate('sent_at', $dateRange->end())
- ->whereDate('created_at', $dateRange->end())
- ->where('rf_department_id', $department->department_id)
- ->first();
+ $report = $this->getReportForPeriod($department->department_id, $dateRange);
+ if (!$report) {
+ return false;
+ }
- return !$dateRange->isEndDateToday() || $reportToday;
+ if ($report->status !== 'submitted') {
+ return false;
+ }
+
+ return true;
}
private function shouldUseReplicaForLiveStatus(User $user, string $status, DateRange $dateRange): bool
@@ -698,6 +804,7 @@ class ReportService
'period_start' => $dateRange->startSql(),
'period_end' => $dateRange->endSql(),
'created_at' => $createdAt,
+ 'status' => $data['status'] ?? 'draft',
];
if (isset($data['reportId']) && $data['reportId']) {
@@ -907,10 +1014,7 @@ class ReportService
*/
public function getCurrentReportInfo(Department $department, User $user, DateRange $dateRange): array
{
- $reportToday = Report::whereDate('sent_at', $dateRange->endSql())
- ->whereDate('created_at', $dateRange->endSql())
- ->where('rf_department_id', $department->department_id)
- ->first();
+ $reportToday = $this->getReportForPeriod($department->department_id, $dateRange);
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
$useSnapshots = $isHeadOrAdmin || !$dateRange->isEndDateToday() || $reportToday;
@@ -931,8 +1035,13 @@ class ReportService
$message = null;
if ($reportToday) {
$reportDoctor = $reportToday->lpuDoctor;
- $message = "Отчет уже создан пользователем: $reportDoctor->FAM_V $reportDoctor->IM_V $reportDoctor->OT_V";
+ $message = "Отчет создан пользователем: $reportDoctor->FAM_V $reportDoctor->IM_V $reportDoctor->OT_V";
}
+ $statusMessage = $reportToday
+ ? ($reportToday->status === 'submitted'
+ ? 'Этот отчет в статусе: опубликован'
+ : 'Этот отчет в статусе: черновик')
+ : null;
// Получаем информацию о враче
$lpuDoctor = $this->getDoctorInfo($fillableUserId, $dateRange);
@@ -957,7 +1066,10 @@ class ReportService
'report_id' => $reportToday?->report_id,
'unwantedEvents' => $unwantedEvents,
'isActiveSendButton' => $isActiveSendButton,
- 'message' => $message,
+ 'message' => $dateRange->isOneDay ? $message : null,
+ 'status' => $reportToday?->status ?? 'draft',
+ 'statusMessage' => $dateRange->isOneDay ? $statusMessage : null,
+ 'canPublish' => (bool) $reportToday && ($reportToday->status === 'draft') && $isActiveSendButton,
'isOneDay' => $dateRange->isOneDay,
'isHeadOrAdmin' => $isHeadOrAdmin,
'dates' => $date,
@@ -983,14 +1095,18 @@ class ReportService
public function createManualPatient(Department $department, User $user, array $data)
{
- return $this->unifiedPatientService->createManualPatient($department, $user, $data);
+ $report = $this->resolveReportForManualPatient($department, $user, $data);
+
+ return $this->unifiedPatientService->createManualPatient($department, $user, $data, $report->report_id);
}
- public function setManualPatientOutcome(int $departmentPatientId, array $data)
+ public function setManualPatientOutcome(User $user, int $departmentPatientId, array $data)
{
$patient = \App\Models\DepartmentPatient::where('department_patient_id', $departmentPatientId)->firstOrFail();
+ $updatedPatient = $this->unifiedPatientService->recordManualOutcome($patient, $data);
+ $this->syncManualPatientSnapshots($updatedPatient, $user, []);
- return $this->unifiedPatientService->recordManualOutcome($patient, $data);
+ return $updatedPatient;
}
public function updateManualPatient(User $user, int $departmentPatientId, array $data)
@@ -1091,20 +1207,18 @@ class ReportService
private function syncManualPatientSnapshots(DepartmentPatient $patient, User $user, array $data): void
{
- if (!isset($data['startAt'], $data['endAt']) || !$data['startAt'] || !$data['endAt']) {
- return;
- }
-
- $dateRange = $this->dateRangeService->getNormalizedDateRange(
- $user,
- (string) $data['startAt'],
- (string) $data['endAt']
- );
-
- $reportIds = $this->getReportsForDateRange($patient->rf_department_id, $dateRange)
- ->pluck('report_id')
- ->values()
- ->all();
+ $reportIds = $patient->rf_report_id
+ ? [$patient->rf_report_id]
+ : (isset($data['startAt'], $data['endAt']) && $data['startAt'] && $data['endAt']
+ ? $this->getReportsForDateRange(
+ $patient->rf_department_id,
+ $this->dateRangeService->getNormalizedDateRange(
+ $user,
+ (string) $data['startAt'],
+ (string) $data['endAt']
+ )
+ )->pluck('report_id')->values()->all()
+ : []);
if (empty($reportIds)) {
return;
@@ -1120,10 +1234,53 @@ class ReportService
'diagnosis_code' => $patient->diagnosis_code,
'diagnosis_name' => $patient->diagnosis_name,
'admitted_at' => $patient->admitted_at,
+ 'outcome_type' => $patient->is_current ? null : $patient->outcome_type,
+ 'outcome_at' => $patient->is_current ? null : $patient->outcome_at,
'updated_at' => now(),
]);
}
+ private function resolveReportForManualPatient(Department $department, User $user, array $data): Report
+ {
+ $reportId = $data['report_id'] ?? null;
+ if ($reportId) {
+ return Report::query()
+ ->where('report_id', $reportId)
+ ->where('rf_department_id', $department->department_id)
+ ->firstOrFail();
+ }
+
+ if (!isset($data['startAt'], $data['endAt']) || !$data['startAt'] || !$data['endAt']) {
+ throw new \InvalidArgumentException('Не указан отчет или диапазон для привязки спецконтингента');
+ }
+
+ $dateRange = $this->dateRangeService->getNormalizedDateRange(
+ $user,
+ (string) $data['startAt'],
+ (string) $data['endAt']
+ );
+
+ $existingReport = Report::query()
+ ->where('rf_department_id', $department->department_id)
+ ->exactPeriod($dateRange->startSql(), $dateRange->endSql())
+ ->first();
+
+ if ($existingReport) {
+ return $existingReport;
+ }
+
+ return Report::query()->create([
+ 'rf_department_id' => $department->department_id,
+ 'rf_user_id' => $user->id,
+ 'rf_lpudoctor_id' => $data['user_id'] ?? $user->rf_lpudoctor_id,
+ 'sent_at' => $dateRange->endSql(),
+ 'created_at' => $dateRange->endSql(),
+ 'period_start' => $dateRange->startSql(),
+ 'period_end' => $dateRange->endSql(),
+ 'status' => 'draft',
+ ]);
+ }
+
/**
* Получить статистику из снапшотов
*/
@@ -1220,9 +1377,7 @@ class ReportService
// ID поступивших сегодня (для отметки в таблице)
$recipientIds = $this->unifiedPatientService
- ->getLivePatientsByStatus($department, $user, 'recipient', $dateRange, $branchId)
- ->pluck('id')
- ->all();
+ ->getRecipientIdsForReport($department, $user, $dateRange, $branchId);
$misBranch = MisStationarBranch::where('StationarBranchID', $branchId)->first();
$beds = Department::where('rf_mis_department_id', $misBranch->rf_DepartmentID)
@@ -1356,27 +1511,15 @@ class ReportService
// Для плановых и экстренных включаем уже лечащихся
$includeCurrent = $isIncludeCurrent ?? in_array($baseStatus, ['plan', 'emergency'], true);
- return match($status) {
- 'plan', 'emergency', 'observation', 'reanimation', 'outcome', 'outcome-discharged', 'outcome-transferred', 'outcome-deceased', 'current', 'recipient' =>
- $this->unifiedPatientService->getLivePatientsByStatus(
- $department,
- $user,
- $status,
- $dateRange,
- $branchId,
- $onlyIds,
- $includeCurrent
- ),
- default => $this->unifiedPatientService->getLivePatientsByStatus(
- $department,
- $user,
- $status,
- $dateRange,
- $branchId,
- $onlyIds,
- $includeCurrent
- )
- };
+ return $this->unifiedPatientService->getLivePatientsByStatus(
+ $department,
+ $user,
+ $status,
+ $dateRange,
+ $branchId,
+ $onlyIds,
+ $includeCurrent
+ );
}
/**
@@ -1534,9 +1677,13 @@ class ReportService
public function getUnwantedEvents(Department $department, DateRange $dateRange)
{
return UnwantedEvent::whereHas('report', function ($query) use ($department, $dateRange) {
- $query->where('rf_department_id', $department->department_id)
- ->whereDate('sent_at', '>=', $dateRange->startSql())
- ->whereDate('sent_at', '<=', $dateRange->endSql());
+ $query->where('rf_department_id', $department->department_id);
+
+ if ($dateRange->isOneDay) {
+ $query->exactPeriod($dateRange->startSql(), $dateRange->endSql());
+ } else {
+ $query->withinPeriod($dateRange->startSql(), $dateRange->endSql());
+ }
})
->get()
->map(function ($item) {
@@ -1554,13 +1701,16 @@ class ReportService
{
// Для врача: только сегодня и если отчета еще нет
if (!$user->isHeadOfDepartment() && !$user->isAdmin()) {
- return $dateRange->isEndDateToday() && !$reportToday;
+ if ($reportToday && $reportToday->status === 'submitted') {
+ return false;
+ }
+
+ return $dateRange->isEndDateToday();
}
- // Для заведующего/админа: если есть отчет & он заполнен текущим пользователем & диапазон дат = 1 дню
+ // Для заведующего/админа: можно редактировать любой отчет за сутки (включая submitted)
if (
$reportToday &&
- $reportToday->rf_lpudoctor_id === intval($fillableUserId) &&
$dateRange->isOneDay
) {
return true;
@@ -1569,6 +1719,19 @@ class ReportService
return false;
}
+ private function getReportForPeriod(int $departmentId, DateRange $dateRange): ?Report
+ {
+ $query = Report::query()
+ ->where('rf_department_id', $departmentId)
+ ->exactPeriod($dateRange->startSql(), $dateRange->endSql())
+ ->orderByDesc('report_id');
+
+ if ($dateRange->isOneDay)
+ return $query->first();
+ else
+ return $query->onlySubmitted()->first();
+ }
+
/**
* Получить информацию о враче
*/
@@ -1593,16 +1756,16 @@ class ReportService
{
if ($dateRange->isOneDay) {
return Report::where('rf_department_id', $departmentId)
- ->whereDate('sent_at', $dateRange->endSql())
- ->orderBy('sent_at', 'DESC')
+ ->exactPeriod($dateRange->startSql(), $dateRange->endSql())
+ ->onlySubmitted()
+ ->orderBy('period_end', 'DESC')
->get();
}
return Report::where('rf_department_id', $departmentId)
-// ->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
- ->where('sent_at', '>', $dateRange->startSql())
- ->where('sent_at', '<=', $dateRange->endSql())
- ->orderBy('sent_at', 'DESC')
+ ->withinPeriod($dateRange->startSql(), $dateRange->endSql())
+ ->onlySubmitted()
+ ->orderBy('period_end', 'DESC')
->get();
}
@@ -1719,15 +1882,15 @@ class ReportService
$progress = 0;
$query = $department->reports()
->with('metrikaResults')
- ->where('sent_at', '>=', $dateRange->startSql())
- ->where('sent_at', '<=', $dateRange->endSql());
+ ->where('period_start', '>', $dateRange->startSql())
+ ->where('period_end', '<=', $dateRange->endSql());
if ($dateRange->isOneDay) {
- $query->where('sent_at', '>=', $dateRange->startFirstOfMonth())
- ->where('sent_at', '<=', $dateRange->endSql());
+ $query->where('period_start', '>=', $dateRange->startFirstOfMonth())
+ ->where('period_end', '<=', $dateRange->endSql());
} else {
- $query->where('sent_at', '>=', $dateRange->startSql())
- ->where('sent_at', '<=', $dateRange->endSql());
+ $query->where('period_start', '>', $dateRange->startSql())
+ ->where('period_end', '<=', $dateRange->endSql());
}
$reports = $query->get();
diff --git a/app/Services/SnapshotService.php b/app/Services/SnapshotService.php
index cc57209..f970d76 100644
--- a/app/Services/SnapshotService.php
+++ b/app/Services/SnapshotService.php
@@ -27,6 +27,12 @@ class SnapshotService
*/
public function createPatientSnapshots(Report $report, User $user, array $dates, $fillableAuto = false): void
{
+ $this->logSnapshotMemory('snapshots:start', [
+ 'report_id' => $report->report_id,
+ 'department_id' => $report->rf_department_id,
+ 'fillable_auto' => (bool) $fillableAuto,
+ ]);
+
$department = Department::query()->where('department_id', $report->rf_department_id)->first() ?? $user->department;
$branchId = $department
? $this->getBranchId($department->rf_mis_department_id)
@@ -39,11 +45,15 @@ class SnapshotService
MedicalHistorySnapshot::query()
->where('rf_report_id', $report->report_id)
->delete();
+ $this->logSnapshotMemory('snapshots:after_delete_old', [
+ 'report_id' => $report->report_id,
+ ]);
[$startDate, $endDate] = $this->parseDates($dates);
$dateRange = $this->dateRangeService->getNormalizedDateRange($user, $startDate, $endDate);
$metrics = [];
+ $this->logSnapshotMemory('snapshots:before_plan_load', ['report_id' => $report->report_id]);
$planPatients = $this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
@@ -52,11 +62,18 @@ class SnapshotService
$branchId,
false,
!$fillableAuto,
- $fillableAuto
+ $fillableAuto,
+ true
);
+ $this->logSnapshotMemory('snapshots:after_plan_load', [
+ 'report_id' => $report->report_id,
+ 'count' => $planPatients->count(),
+ ]);
$this->createSnapshotsForType($report, 'plan', $planPatients);
+ $this->logSnapshotMemory('snapshots:after_plan_save', ['report_id' => $report->report_id]);
$metrics[4] = $planPatients->count();
+ $this->logSnapshotMemory('snapshots:before_emergency_load', ['report_id' => $report->report_id]);
$emergencyPatients = $this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
@@ -65,11 +82,18 @@ class SnapshotService
$branchId,
false,
!$fillableAuto,
- $fillableAuto
+ $fillableAuto,
+ true
);
+ $this->logSnapshotMemory('snapshots:after_emergency_load', [
+ 'report_id' => $report->report_id,
+ 'count' => $emergencyPatients->count(),
+ ]);
$this->createSnapshotsForType($report, 'emergency', $emergencyPatients);
+ $this->logSnapshotMemory('snapshots:after_emergency_save', ['report_id' => $report->report_id]);
$metrics[12] = $emergencyPatients->count();
+ $this->logSnapshotMemory('snapshots:before_discharged_load', ['report_id' => $report->report_id]);
$dischargedPatients = $this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
@@ -78,11 +102,18 @@ class SnapshotService
$branchId,
false,
null,
- $fillableAuto
+ $fillableAuto,
+ true
);
+ $this->logSnapshotMemory('snapshots:after_discharged_load', [
+ 'report_id' => $report->report_id,
+ 'count' => $dischargedPatients->count(),
+ ]);
$this->createSnapshotsForType($report, 'discharged', $dischargedPatients);
+ $this->logSnapshotMemory('snapshots:after_discharged_save', ['report_id' => $report->report_id]);
$metrics[15] = $dischargedPatients->count();
+ $this->logSnapshotMemory('snapshots:before_transferred_load', ['report_id' => $report->report_id]);
$transferredPatients = $this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
@@ -91,11 +122,18 @@ class SnapshotService
$branchId,
false,
null,
- $fillableAuto
+ $fillableAuto,
+ true
);
+ $this->logSnapshotMemory('snapshots:after_transferred_load', [
+ 'report_id' => $report->report_id,
+ 'count' => $transferredPatients->count(),
+ ]);
$this->createSnapshotsForType($report, 'transferred', $transferredPatients);
+ $this->logSnapshotMemory('snapshots:after_transferred_save', ['report_id' => $report->report_id]);
$metrics[13] = $transferredPatients->count();
+ $this->logSnapshotMemory('snapshots:before_deceased_load', ['report_id' => $report->report_id]);
$deceasedPatients = $this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
@@ -104,10 +142,17 @@ class SnapshotService
$branchId,
false,
null,
- $fillableAuto
+ $fillableAuto,
+ true
);
+ $this->logSnapshotMemory('snapshots:after_deceased_load', [
+ 'report_id' => $report->report_id,
+ 'count' => $deceasedPatients->count(),
+ ]);
$this->createSnapshotsForType($report, 'deceased', $deceasedPatients);
+ $this->logSnapshotMemory('snapshots:after_deceased_save', ['report_id' => $report->report_id]);
+ $this->logSnapshotMemory('snapshots:before_recipient_load', ['report_id' => $report->report_id]);
$recipientPatients = $this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
@@ -116,10 +161,17 @@ class SnapshotService
$branchId,
false,
null,
- $fillableAuto
+ $fillableAuto,
+ true
);
+ $this->logSnapshotMemory('snapshots:after_recipient_load', [
+ 'report_id' => $report->report_id,
+ 'count' => $recipientPatients->count(),
+ ]);
$this->createSnapshotsForType($report, 'recipient', $recipientPatients);
+ $this->logSnapshotMemory('snapshots:after_recipient_save', ['report_id' => $report->report_id]);
+ $this->logSnapshotMemory('snapshots:before_current_load', ['report_id' => $report->report_id]);
$currentPatients = $this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
@@ -128,9 +180,15 @@ class SnapshotService
$branchId,
false,
null,
- $fillableAuto
+ $fillableAuto,
+ true
);
+ $this->logSnapshotMemory('snapshots:after_current_load', [
+ 'report_id' => $report->report_id,
+ 'count' => $currentPatients->count(),
+ ]);
$this->createSnapshotsForType($report, 'current', $currentPatients);
+ $this->logSnapshotMemory('snapshots:after_current_save', ['report_id' => $report->report_id]);
$planSurgeryCount = $this->patientService->getSurgicalPatients(
'plan',
@@ -146,6 +204,17 @@ class SnapshotService
);
$this->saveMetrics($report, $metrics);
+ $this->logSnapshotMemory('snapshots:after_save_metrics', ['report_id' => $report->report_id]);
+ }
+
+ private function logSnapshotMemory(string $stage, array $context = []): void
+ {
+ \Log::info('report.snapshot.memory', [
+ 'stage' => $stage,
+ 'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
+ 'peak_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
+ ...$context,
+ ]);
}
/**
diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php
index 6806a93..50247e2 100644
--- a/app/Services/StatisticsService.php
+++ b/app/Services/StatisticsService.php
@@ -63,8 +63,9 @@ class StatisticsService
$metrics = DB::table('reports as r')
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $allDeptIds)
- ->whereDate('r.sent_at', '>=', $startDate)
- ->whereDate('r.sent_at', '<=', $endDate)
+ ->where('r.period_start', '>=', $startDate)
+ ->where('r.period_start', '<=', $endDate)
+ ->where('r.status', 'submitted')
->select(
'r.rf_department_id',
'mr.rf_metrika_item_id',
@@ -81,10 +82,10 @@ class StatisticsService
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $allDeptIds)
->where('mr.rf_metrika_item_id', 8)
- ->where('r.sent_at', '<=', $endDate)
+ ->where('r.period_start', '<=', $endDate)
->select('r.rf_department_id', 'mr.value', 'r.created_at')
->orderBy('r.rf_department_id') // Сначала поле из DISTINCT ON
- ->orderBy('r.sent_at', 'desc') // Потом остальные
+ ->orderBy('r.period_end', 'desc') // Потом остальные
->distinct('r.rf_department_id')
->get()
->keyBy('rf_department_id');
@@ -109,10 +110,16 @@ class StatisticsService
foreach ($deptList as $dept) {
$deptId = $dept->department_id;
- $lastReport = Report::where('rf_department_id', $deptId)
- ->whereDate('sent_at', '>=', Carbon::parse($startDate)->format('Y-m-d'))
- ->whereDate('sent_at', '<=', Carbon::parse($endDate)->format('Y-m-d'))
- ->orderBy('sent_at', 'desc')
+ $lastReportQuery = Report::where('rf_department_id', $deptId);
+ if ($isRangeOneDay) {
+ $lastReportQuery->exactPeriod($startDate, $endDate);
+ } else {
+ $lastReportQuery->withinPeriod($startDate, $endDate);
+ }
+
+ $lastReport = $lastReportQuery
+ ->onlySubmitted()
+ ->orderBy('period_end', 'desc')
->first();
// Базовые показатели
@@ -141,6 +148,7 @@ class StatisticsService
$observable = 0;
$unwanted = 0;
$bedDaysSum = 0;
+ $avgBedDays = 0;
if (isset($metrics[$deptId])) {
foreach ($metrics[$deptId] as $item) {
@@ -219,7 +227,7 @@ class StatisticsService
'lethality' => $lethality,
'type' => $typeName,
'isDepartment' => true,
- 'isReportToday' => $lastReport ? Carbon::parse($lastReport->sent_at)->isSameDay($endDate) : null,
+ 'isReportToday' => $lastReport ? Carbon::parse($lastReport->period_end)->isSameDay($endDate) : null,
];
$groupedData[$typeName][] = $data;
diff --git a/app/Services/UnifiedPatientService.php b/app/Services/UnifiedPatientService.php
index 294ce63..4c5e02a 100644
--- a/app/Services/UnifiedPatientService.php
+++ b/app/Services/UnifiedPatientService.php
@@ -7,6 +7,7 @@ use App\Models\Department;
use App\Models\DepartmentPatient;
use App\Models\MisMedicalHistory;
use App\Models\ObservationPatient;
+use App\Models\Report;
use App\Models\User;
use Illuminate\Support\Collection;
@@ -26,7 +27,8 @@ class UnifiedPatientService
int $branchId,
bool $onlyIds = false,
?bool $includeCurrent = null,
- bool $fillableAuto = false
+ bool $fillableAuto = false,
+ bool $forSnapshots = false
): Collection {
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
@@ -35,9 +37,9 @@ class UnifiedPatientService
}
$patients = match ($sourceScope) {
- 'mis' => $this->getMisPatientDtos($user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto),
- 'special' => $this->getSpecialPatientDtos($department, $baseStatus, $dateRange),
- default => $this->getAggregatedPatientDtos($department, $user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto),
+ 'mis' => $this->getMisPatientDtos($user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots),
+ 'special' => $this->getSpecialPatientDtos($department, $baseStatus, $dateRange, $forSnapshots),
+ default => $this->getAggregatedPatientDtos($department, $user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots),
};
if ($onlyIds) {
@@ -56,13 +58,79 @@ class UnifiedPatientService
?bool $includeCurrent = null,
bool $fillableAuto = false
): int {
- return $this->getLivePatientsByStatus($department, $user, $status, $dateRange, $branchId, false, $includeCurrent, $fillableAuto)->count();
+ [$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
+
+ if ($baseStatus === 'observation') {
+ $query = ObservationPatient::query()
+ ->where('rf_department_id', $department->department_id);
+
+ if ($sourceScope === 'special') {
+ return $query->whereNotNull('rf_department_patient_id')->count();
+ }
+
+ if ($sourceScope === 'mis') {
+ return $query->whereNull('rf_department_patient_id')->count();
+ }
+
+ return $query->count();
+ }
+
+ if ($sourceScope === 'special') {
+ return $this->getManualPatientsCount($department, $baseStatus, $dateRange, self::SPECIAL_SOURCE_TYPES);
+ }
+
+ $misCount = $this->getMisPatientsCount(
+ $user,
+ $baseStatus,
+ $dateRange,
+ $branchId,
+ $includeCurrent,
+ $fillableAuto
+ );
+
+ if ($sourceScope === 'mis') {
+ return $misCount;
+ }
+
+ $specialCount = $this->getManualPatientsCount($department, $baseStatus, $dateRange, self::SPECIAL_SOURCE_TYPES);
+ return $misCount + $specialCount;
}
- public function createManualPatient(Department $department, User $user, array $data): DepartmentPatient
+ public function getRecipientIdsForReport(
+ Department $department,
+ User $user,
+ DateRange $dateRange,
+ int $branchId,
+ bool $fillableAuto = false
+ ): array {
+ $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
+
+ $misIds = $this->patientService->getPlanOrEmergencyPatients(
+ null,
+ $isHeadOrAdmin,
+ $branchId,
+ $dateRange,
+ false,
+ true,
+ false,
+ $fillableAuto
+ );
+
+ $manualIds = $this->buildManualPatientsQuery($department, $dateRange, self::SPECIAL_SOURCE_TYPES, false)
+ ->whereBetween('admitted_at', [$dateRange->startSql(), $dateRange->endSql()])
+ ->pluck('department_patient_id');
+
+ $misUids = collect($misIds)->map(fn ($id) => "mis:{$id}")->all();
+ $manualUids = collect($manualIds)->map(fn ($id) => "manual:{$id}")->all();
+
+ return array_values(array_unique(array_merge($misUids, $manualUids)));
+ }
+
+ public function createManualPatient(Department $department, User $user, array $data, int $reportId): DepartmentPatient
{
return DepartmentPatient::create([
'rf_department_id' => $department->department_id,
+ 'rf_report_id' => $reportId,
'source_type' => 'special',
'full_name' => $data['full_name'],
'birth_date' => $data['birth_date'],
@@ -88,6 +156,13 @@ class UnifiedPatientService
public function updateManualPatient(DepartmentPatient $patient, array $data): DepartmentPatient
{
+ $manualStatus = $data['manual_status'] ?? null;
+ $isCurrent = $manualStatus === 'current' || $manualStatus === null;
+ $outcomeType = match ($manualStatus) {
+ 'discharged', 'deceased', 'transferred' => $manualStatus,
+ default => null,
+ };
+
$patient->update([
'full_name' => $data['full_name'],
'birth_date' => $data['birth_date'],
@@ -95,6 +170,9 @@ class UnifiedPatientService
'diagnosis_code' => $data['diagnosis_code'] ?? null,
'diagnosis_name' => $data['diagnosis_name'] ?? null,
'admitted_at' => $data['admitted_at'] ?? $patient->admitted_at,
+ 'is_current' => $isCurrent,
+ 'outcome_type' => $outcomeType,
+ 'outcome_at' => $isCurrent ? null : ($data['outcome_at'] ?? now()),
]);
return $patient->fresh();
@@ -194,11 +272,24 @@ class UnifiedPatientService
DateRange $dateRange,
int $branchId,
?bool $includeCurrent = null,
- bool $fillableAuto = false
+ bool $fillableAuto = false,
+ bool $forSnapshots = false
): Collection {
- $misPatients = $this->getMisPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto);
- $manualPatients = $this->getManualPatients($department, $status, $dateRange);
- $linkedManualPatients = DepartmentPatient::where('rf_department_id', $department->department_id)
+ $misPatients = $this->getMisPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots);
+ $manualPatients = $this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES, !$forSnapshots);
+ $reportIds = $this->getReportIdsForDepartmentPeriod($department, $dateRange);
+
+ $linkedManualPatients = DepartmentPatient::query()
+ ->where(function ($builder) use ($department, $reportIds) {
+ if (!empty($reportIds)) {
+ $builder->whereIn('rf_report_id', $reportIds);
+ }
+
+ $builder->orWhere(function ($legacyQuery) use ($department) {
+ $legacyQuery->whereNull('rf_report_id')
+ ->where('rf_department_id', $department->department_id);
+ });
+ })
->whereIn('source_type', self::SPECIAL_SOURCE_TYPES)
->whereNotNull('rf_medicalhistory_id')
->get()
@@ -228,9 +319,10 @@ class UnifiedPatientService
DateRange $dateRange,
int $branchId,
?bool $includeCurrent = null,
- bool $fillableAuto = false
+ bool $fillableAuto = false,
+ bool $forSnapshots = false
): Collection {
- return $this->getMisPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto)
+ return $this->getMisPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots)
->map(fn ($patient) => UnifiedPatientData::fromMisMedicalHistory(
$patient,
(bool) ($patient->is_recipient_today ?? false),
@@ -242,10 +334,11 @@ class UnifiedPatientService
private function getSpecialPatientDtos(
Department $department,
string $status,
- DateRange $dateRange
+ DateRange $dateRange,
+ bool $forSnapshots = false
): Collection {
return $this->mapManualPatients(
- $this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES),
+ $this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES, !$forSnapshots),
$dateRange
);
}
@@ -256,7 +349,8 @@ class UnifiedPatientService
DateRange $dateRange,
int $branchId,
?bool $includeCurrent = null,
- bool $fillableAuto = false
+ bool $fillableAuto = false,
+ bool $forSnapshots = false
): Collection {
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
$includeCurrent = $includeCurrent ?? in_array($status, ['plan', 'emergency'], true);
@@ -299,19 +393,66 @@ class UnifiedPatientService
};
}
+ private function getMisPatientsCount(
+ User $user,
+ string $status,
+ DateRange $dateRange,
+ int $branchId,
+ ?bool $includeCurrent = null,
+ bool $fillableAuto = false
+ ): int {
+ $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
+ $includeCurrent = $includeCurrent ?? in_array($status, ['plan', 'emergency'], true);
+
+ return match ($status) {
+ 'plan', 'emergency' => $includeCurrent
+ ? $this->patientService->getPatientsCountWithCurrent($status, $isHeadOrAdmin, $branchId, $dateRange)
+ : $this->patientService->getPlanOrEmergencyPatients(
+ $status,
+ $isHeadOrAdmin,
+ $branchId,
+ $dateRange,
+ true,
+ false,
+ false,
+ $fillableAuto
+ ),
+ 'current' => $this->patientService->getAllPatientsInDepartment(
+ $isHeadOrAdmin,
+ $branchId,
+ $dateRange,
+ true,
+ false,
+ $fillableAuto
+ ),
+ 'recipient' => $this->patientService->getPlanOrEmergencyPatients(
+ null,
+ $isHeadOrAdmin,
+ $branchId,
+ $dateRange,
+ true,
+ false,
+ false,
+ $fillableAuto
+ ),
+ 'outcome' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'without-transferred', true)->count(),
+ 'outcome-discharged' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'discharged', true)->count(),
+ 'outcome-transferred' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'transferred', true)->count(),
+ 'outcome-deceased' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'deceased', true)->count(),
+ 'reanimation' => $this->patientService->getReanimationPatients($branchId, $dateRange, true)->count(),
+ default => 0,
+ };
+ }
+
private function getManualPatients(
Department $department,
string $status,
DateRange $dateRange,
- ?array $sourceTypes = self::SPECIAL_SOURCE_TYPES
+ ?array $sourceTypes = self::SPECIAL_SOURCE_TYPES,
+ bool $withOperations = true
): Collection
{
- $query = DepartmentPatient::where('rf_department_id', $department->department_id)
- ->with(['operations.serviceMedical']);
-
- if ($sourceTypes !== null) {
- $query->whereIn('source_type', $sourceTypes);
- }
+ $query = $this->buildManualPatientsQuery($department, $dateRange, $sourceTypes, $withOperations);
return match ($status) {
'plan', 'emergency' => $query
@@ -338,11 +479,91 @@ class UnifiedPatientService
};
}
+ private function getManualPatientsCount(
+ Department $department,
+ string $status,
+ DateRange $dateRange,
+ ?array $sourceTypes = self::SPECIAL_SOURCE_TYPES
+ ): int {
+ $query = $this->buildManualPatientsQuery($department, $dateRange, $sourceTypes, false);
+
+ return match ($status) {
+ 'plan', 'emergency' => $query
+ ->current()
+ ->where('patient_kind', $status)
+ ->count(),
+ 'current' => $query
+ ->current()
+ ->count(),
+ 'recipient' => $query
+ ->whereBetween('admitted_at', [$dateRange->startSql(), $dateRange->endSql()])
+ ->count(),
+ 'outcome' => $query
+ ->whereNotNull('outcome_type')
+ ->whereIn('outcome_type', ['discharged', 'deceased'])
+ ->whereBetween('outcome_at', [$dateRange->startSql(), $dateRange->endSql()])
+ ->count(),
+ 'outcome-discharged', 'outcome-transferred', 'outcome-deceased' => $query
+ ->where('outcome_type', str_replace('outcome-', '', $status))
+ ->whereBetween('outcome_at', [$dateRange->startSql(), $dateRange->endSql()])
+ ->count(),
+ default => 0,
+ };
+ }
+
+ private function buildManualPatientsQuery(
+ Department $department,
+ DateRange $dateRange,
+ ?array $sourceTypes,
+ bool $withOperations
+ ) {
+ $reportIds = $this->getReportIdsForDepartmentPeriod($department, $dateRange);
+
+ $query = DepartmentPatient::query()
+ ->where(function ($builder) use ($department, $reportIds) {
+ if (!empty($reportIds)) {
+ $builder->whereIn('rf_report_id', $reportIds);
+ }
+
+ $builder->orWhere(function ($legacyQuery) use ($department) {
+ $legacyQuery->whereNull('rf_report_id')
+ ->where('rf_department_id', $department->department_id);
+ });
+ });
+
+ if ($withOperations) {
+ $query->with(['operations.serviceMedical']);
+ }
+
+ if ($sourceTypes !== null) {
+ $query->whereIn('source_type', $sourceTypes);
+ }
+
+ return $query;
+ }
+
+ private function getReportIdsForDepartmentPeriod(Department $department, DateRange $dateRange): array
+ {
+ return Report::query()
+ ->where('rf_department_id', $department->department_id)
+ ->when(
+ $dateRange->isOneDay,
+ fn ($query) => $query->exactPeriod($dateRange->startSql(), $dateRange->endSql()),
+ fn ($query) => $query->withinPeriod($dateRange->startSql(), $dateRange->endSql()),
+ )
+ ->pluck('report_id')
+ ->all();
+ }
+
private function mapManualPatients(Collection $manualPatients, DateRange $dateRange): Collection
{
return $manualPatients
->map(function (DepartmentPatient $patient) use ($dateRange) {
- $operations = $patient->operations->map(fn ($operation) => [
+ $operationsRelation = $patient->relationLoaded('operations')
+ ? $patient->operations
+ : collect();
+
+ $operations = $operationsRelation->map(fn ($operation) => [
'id' => $operation->department_patient_operation_id,
'code' => $operation->serviceMedical?->ServiceMedicalCode ?? $operation->service_code,
'name' => $operation->serviceMedical?->ServiceMedicalName ?? $operation->service_name,
diff --git a/database/migrations/2026_04_20_163507_add_code_column_in_metrika_items_table.php b/database/migrations/2026_04_20_163507_add_code_column_in_metrika_items_table.php
index 1c1ef7e..789d46f 100644
--- a/database/migrations/2026_04_20_163507_add_code_column_in_metrika_items_table.php
+++ b/database/migrations/2026_04_20_163507_add_code_column_in_metrika_items_table.php
@@ -22,7 +22,7 @@ return new class extends Migration
public function down(): void
{
Schema::table('metrika_items', function (Blueprint $table) {
- //
+ $table->dropColumn('code');
});
}
};
diff --git a/resources/js/Components/AppGrid.vue b/resources/js/Components/AppGrid.vue
new file mode 100644
index 0000000..8f6692f
--- /dev/null
+++ b/resources/js/Components/AppGrid.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/Components/AppGridItem.vue b/resources/js/Components/AppGridItem.vue
new file mode 100644
index 0000000..103013a
--- /dev/null
+++ b/resources/js/Components/AppGridItem.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/Components/AppPanel.vue b/resources/js/Components/AppPanel.vue
index 8adfa78..9e68a07 100644
--- a/resources/js/Components/AppPanel.vue
+++ b/resources/js/Components/AppPanel.vue
@@ -6,6 +6,10 @@ const props = defineProps({
type: String,
default: ''
},
+ headerIncludeBody: {
+ type: Boolean,
+ default: false
+ },
feedback: {
type: String,
default: ''
@@ -25,6 +29,7 @@ const props = defineProps({
})
const hasHeader = computed(() => props.header.trim().length > 0)
+const hasHeaderInOutside = computed(() => hasHeader.value && !props.headerIncludeBody)
const hasFeedback = computed(() => props.feedback.trim().length > 0)
const hasMinH = computed(() => props.minH.trim().length > 0 || Number.isInteger(props.minH))
const hasMaxH = computed(() => props.maxH.trim().length > 0 || Number.isInteger(props.maxH))
@@ -55,8 +60,11 @@ watch(() => [props.minH, props.maxH], ([minH, maxH]) => {
-
+
+
+ {{ header }}
+
diff --git a/resources/js/Pages/Report/Components/ManualPatientEditModal.vue b/resources/js/Pages/Report/Components/ManualPatientEditModal.vue
index c00d371..b4ce1b3 100644
--- a/resources/js/Pages/Report/Components/ManualPatientEditModal.vue
+++ b/resources/js/Pages/Report/Components/ManualPatientEditModal.vue
@@ -27,6 +27,8 @@ const form = reactive({
birth_date: null,
admitted_at: null,
patient_kind: 'plan',
+ manual_status: 'current',
+ outcome_at: null,
diagnosis_code: '',
diagnosis_name: '',
})
@@ -56,6 +58,12 @@ const patientKindOptions = [
{label: 'Экстренно', value: 'emergency'},
]
+const manualStatusOptions = [
+ {label: 'Состоит', value: 'current'},
+ {label: 'Выписан', value: 'discharged'},
+ {label: 'Умер', value: 'deceased'},
+]
+
const normalizeSpaces = (value) => value.replace(/\s+/g, ' ').trim()
const pad = (value) => String(value).padStart(2, '0')
const formatLocalDate = (timestamp) => {
@@ -76,6 +84,11 @@ const parseDateToTimestamp = (value) => {
}
const parseDateTimeToTimestamp = (value) => {
if (!value) return null
+ const nativeDate = new Date(value)
+ if (!Number.isNaN(nativeDate.getTime())) {
+ return nativeDate.getTime()
+ }
+
const [datePart, timePart] = String(value).split(' ')
const dateTimestamp = parseDateToTimestamp(datePart)
if (!dateTimestamp) return null
@@ -90,6 +103,8 @@ const hydrateForm = () => {
form.birth_date = parseDateToTimestamp(props.patient?.birth_date)
form.admitted_at = parseDateTimeToTimestamp(props.patient?.admitted_at)
form.patient_kind = props.patient?.patient_kind ?? 'plan'
+ form.manual_status = props.patient?.outcome_type ?? 'current'
+ form.outcome_at = parseDateTimeToTimestamp(props.patient?.outcome_date)
form.diagnosis_code = props.patient?.mkb?.ds ?? ''
form.diagnosis_name = props.patient?.mkb?.name ?? ''
@@ -179,11 +194,21 @@ const submit = async () => {
birth_date: formatLocalDate(form.birth_date),
admitted_at: form.admitted_at ? formatLocalDateTime(form.admitted_at) : null,
patient_kind: form.patient_kind,
+ manual_status: form.manual_status,
+ outcome_at: form.manual_status === 'current'
+ ? null
+ : (form.outcome_at ? formatLocalDateTime(form.outcome_at) : null),
diagnosis_code: form.diagnosis_code ? normalizeSpaces(form.diagnosis_code).toUpperCase() : null,
diagnosis_name: form.diagnosis_name ? normalizeSpaces(form.diagnosis_name) : null,
},
{
- reloadStatuses: [props.sourceStatus, `special-${form.patient_kind}`],
+ reloadStatuses: [
+ props.sourceStatus,
+ `special-${form.patient_kind}`,
+ 'special-outcome-discharged',
+ 'special-outcome-deceased',
+ 'special-outcome-transferred',
+ ],
}
)
@@ -204,12 +229,18 @@ const submit = async () => {
+
+
+
+
+
+
{
try {
await reportStore.createManualPatient({
departmentId: reportStore.reportInfo.department.department_id,
+ report_id: reportStore.reportInfo?.report?.report_id ?? null,
+ startAt: reportStore.reportInfo?.dates?.startAt,
+ endAt: reportStore.reportInfo?.dates?.endAt,
+ user_id: reportStore.reportInfo?.report?.userId ?? null,
full_name: normalizeSpaces(form.full_name),
birth_date: formatLocalDate(form.birth_date),
patient_kind: props.patientKind,
diff --git a/resources/js/Pages/Report/Components/ReportForm.vue b/resources/js/Pages/Report/Components/ReportForm.vue
index 3a1719f..ea99f64 100644
--- a/resources/js/Pages/Report/Components/ReportForm.vue
+++ b/resources/js/Pages/Report/Components/ReportForm.vue
@@ -25,23 +25,44 @@ const onSubmit = () => {
// reportStore.sendReportForm()
}
+const onPublish = () => {
+ reportStore.reportFormRef?.validate((errors) => {
+ if (!errors) reportStore.sendReportForm({ status: 'submitted' })
+ else window.$message.error('Ошибка отправки отчета')
+ })
+}
-
- {{ reportStore.reportInfo.report.message }}
-
+
+
+ {{ reportStore.reportInfo.report.message }}
+
+
+ {{ reportStore.reportInfo.report.statusMessage }}
+
+
-
- Сохранить отчет
-
+
+
+ Сохранить отчет
+
+
+ Опубликовать
+
+
diff --git a/resources/js/Pages/Report/Components/ReportSection.vue b/resources/js/Pages/Report/Components/ReportSection.vue
index e51d592..8b28bc5 100644
--- a/resources/js/Pages/Report/Components/ReportSection.vue
+++ b/resources/js/Pages/Report/Components/ReportSection.vue
@@ -217,14 +217,6 @@ watch(
-
-
-
-
-
-
diff --git a/resources/js/Pages/Report/Components/ReportSectionItem.vue b/resources/js/Pages/Report/Components/ReportSectionItem.vue
index 71aef3e..e96059c 100644
--- a/resources/js/Pages/Report/Components/ReportSectionItem.vue
+++ b/resources/js/Pages/Report/Components/ReportSectionItem.vue
@@ -18,7 +18,7 @@ import {
import {useReportStore} from "../../../Stores/report.js";
import {computed, h, ref, watch} from "vue";
import {storeToRefs} from "pinia";
-import {TbEye, TbExternalLink, TbPencil} from "vue-icons-plus/tb";
+import {TbEye, TbExternalLink, TbPencil, TbTrash} from "vue-icons-plus/tb";
import MoveModalComment from "./MoveModalComment.vue";
import OperationInfoModal from "./OperationInfoModal.vue";
import ManualPatientOutcomeModal from "./ManualPatientOutcomeModal.vue";
@@ -90,7 +90,11 @@ const activePatient = ref(null)
const hasDisabledEdit = computed(() => {
return !Boolean(reportStore.reportInfo?.report?.isActiveSendButton)
})
-const canEditSpecial = computed(() => isSpecialStatus.value && !hasDisabledEdit.value)
+const canEditSpecial = computed(() => (
+ isSpecialStatus.value
+ && !hasDisabledEdit.value
+ && baseStatus.value !== 'observation'
+))
const statusState = computed(() => statusStates.value[props.status] ?? {
page: 1,
perPage: 20,
@@ -214,7 +218,7 @@ const columns = computed(() => {
}
},
[
- 'Снять с наблюдения'
+ h(NIcon, {size: '16'}, h(TbTrash))
]
)
}
diff --git a/resources/js/Pages/TestIndex.vue b/resources/js/Pages/TestIndex.vue
new file mode 100644
index 0000000..1efdb01
--- /dev/null
+++ b/resources/js/Pages/TestIndex.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ panel
+
+
+
+
+ panel
+
+
+
+
+
+
+
+
diff --git a/resources/js/Stores/report.js b/resources/js/Stores/report.js
index b9aefe9..5cb1d1a 100644
--- a/resources/js/Stores/report.js
+++ b/resources/js/Stores/report.js
@@ -14,7 +14,6 @@ const patientStatuses = [
'special-plan',
'special-emergency',
'special-observation',
- 'special-reanimation',
'special-outcome-discharged',
'special-outcome-deceased',
'special-outcome-transferred',
@@ -260,6 +259,17 @@ export const useReportStore = defineStore('reportStore', () => {
})
})
+ const getSpecialStatusByPatient = (patient) => {
+ if (!patient) return null
+
+ if (patient.is_current) {
+ return patient.patient_kind ? `special-${patient.patient_kind}` : null
+ }
+
+ if (!patient.outcome_type) return null
+ return `special-outcome-${patient.outcome_type}`
+ }
+
const getDataOnReportDate = async () => {
await reloadReportPage()
}
@@ -276,19 +286,30 @@ export const useReportStore = defineStore('reportStore', () => {
userId: reportInfo.value.report.userId,
departmentId: reportInfo.value.department.department_id,
reportId: reportInfo.value.report.report_id,
+ status: reportInfo.value?.report?.status ?? 'draft',
...assignForm
}
router.post('/report', form, {
onSuccess: () => {
- window.$message.success('Отчет сохранен')
+ window.$message.success(form.status === 'submitted' ? 'Отчет опубликован' : 'Черновик сохранен')
}
})
}
const createManualPatient = async (payload) => {
- await axios.post('/api/report/manual-patients', payload)
- await reloadReportPage()
+ const response = await axios.post('/api/report/manual-patients', payload)
+ const reportId = response.data?.report_id
+
+ if (reportId) {
+ reportInfo.value = {
+ ...reportInfo.value,
+ report: {
+ ...(reportInfo.value?.report ?? {}),
+ report_id: reportId,
+ }
+ }
+ }
const status = `special-${payload.patient_kind}`
@@ -299,9 +320,13 @@ export const useReportStore = defineStore('reportStore', () => {
}
const setManualPatientOutcome = async (departmentPatientId, payload) => {
- await axios.post(`/api/report/manual-patients/${departmentPatientId}/outcome`, payload)
- await reloadReportPage()
- await loadAllStatusCounts(true)
+ const response = await axios.post(`/api/report/manual-patients/${departmentPatientId}/outcome`, payload)
+ const targetStatus = getSpecialStatusByPatient(response.data)
+
+ await Promise.all([
+ ...(targetStatus ? [loadPatientsByStatus(targetStatus, { resetPage: true })] : []),
+ loadAllStatusCounts(true),
+ ])
}
const updateManualPatient = async (departmentPatientId, payload, options = {}) => {
@@ -313,8 +338,6 @@ export const useReportStore = defineStore('reportStore', () => {
const reloadStatuses = Array.from(new Set((options.reloadStatuses ?? []).filter(Boolean)))
- await reloadReportPage()
-
await Promise.all([
...reloadStatuses.map((status) => loadPatientsByStatus(status, { resetPage: true })),
loadAllStatusCounts(true),
diff --git a/routes/web.php b/routes/web.php
index 8055989..d03ae20 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -17,6 +17,7 @@ Route::middleware('guest')->group(function () {
Route::prefix('test')->group(function () {
Route::get('/', [TestController::class, 'testMigrations']);
+ Route::get('/i', [TestController::class, 'testIndex']);
});
Route::prefix('api')->group(function () {