diff --git a/app/Infrastructure/Reports/Sources/SnapshotPatientSource.php b/app/Infrastructure/Reports/Sources/SnapshotPatientSource.php index d5cccfb..0847a86 100644 --- a/app/Infrastructure/Reports/Sources/SnapshotPatientSource.php +++ b/app/Infrastructure/Reports/Sources/SnapshotPatientSource.php @@ -31,6 +31,7 @@ class SnapshotPatientSource ?array $recipientReportIds = null ): Collection { $snapshots = MedicalHistorySnapshot::query() + ->withoutDenials() ->whereIn('rf_report_id', $reportIds) ->where('patient_type', $type) ->get() @@ -45,6 +46,7 @@ class SnapshotPatientSource if ($markRecipients) { $recipientReportIds ??= $reportIds; $recipientIds = MedicalHistorySnapshot::query() + ->withoutDenials() ->whereIn('rf_report_id', $recipientReportIds) ->where('patient_type', 'recipient') ->get() @@ -91,6 +93,7 @@ class SnapshotPatientSource ?array $recipientReportIds = null ): Collection { $snapshots = MedicalHistorySnapshot::query() + ->withoutDenials() ->whereIn('rf_report_id', $reportIds) ->where('patient_type', 'current') ->get(); @@ -107,6 +110,7 @@ class SnapshotPatientSource $recipientReportIds ??= $reportIds; $recipientIds = MedicalHistorySnapshot::query() + ->withoutDenials() ->whereIn('rf_report_id', $recipientReportIds) ->where('patient_type', 'recipient') ->get() @@ -151,7 +155,7 @@ class SnapshotPatientSource private function getCountFromSnapshots(string $type, array $reportIds): int { - $query = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds); + $query = MedicalHistorySnapshot::withoutDenials()->whereIn('rf_report_id', $reportIds); if ($type === 'outcome') { $query->whereIn('patient_type', ['discharged', 'deceased']); diff --git a/app/Models/MedicalHistory.php b/app/Models/MedicalHistory.php index 0dc0e90..041d458 100644 --- a/app/Models/MedicalHistory.php +++ b/app/Models/MedicalHistory.php @@ -53,6 +53,24 @@ class MedicalHistory extends MaterializedViewModel ->latest('observable_in'); } + public function denial() + { + return $this->hasOne(MisDenial::class, 'rf_MedicalHistoryID', 'id'); + } + + /** + * Исключить отказников от госпитализации. + * Безопасно: пока таблицы отказов нет в реплике — no-op (см. MisDenial::tableAvailable()). + */ + public function scopeWithoutDenials($query) + { + if (MisDenial::tableAvailable()) { + $query->whereDoesntHave('denial'); + } + + return $query; + } + public function operationsInDepartment($query, $departmentId) { return $this->operations()->where('department_id', $departmentId); diff --git a/app/Models/MedicalHistorySnapshot.php b/app/Models/MedicalHistorySnapshot.php index 18d5b9c..078b50f 100644 --- a/app/Models/MedicalHistorySnapshot.php +++ b/app/Models/MedicalHistorySnapshot.php @@ -72,4 +72,25 @@ class MedicalHistorySnapshot extends Model $q->department($departmentId); }); } + + /** + * Исключить из снимка карты, по которым в МИС оформлен отказ от госпитализации. + * Снимок остаётся неизменным (immutable) — отсекаем только при чтении. + * Безопасно: пока таблицы отказов нет в реплике — no-op (см. MisDenial::tableAvailable()). + */ + public function scopeWithoutDenials($query) + { + if (! MisDenial::tableAvailable()) { + return $query; + } + + $denialTable = (new MisDenial)->getTable(); + $snapshotTable = $this->getTable(); + + return $query->whereNotExists(function ($sub) use ($denialTable, $snapshotTable) { + $sub->selectRaw('1') + ->from($denialTable) + ->whereColumn("{$denialTable}.rf_MedicalHistoryID", "{$snapshotTable}.rf_medicalhistory_id"); + }); + } } diff --git a/app/Models/MisDenial.php b/app/Models/MisDenial.php new file mode 100644 index 0000000..c67b668 --- /dev/null +++ b/app/Models/MisDenial.php @@ -0,0 +1,41 @@ + mv_medicalhistory_summary.id, + * rf_kl_HospRefusalID, DateTime, FAM/Name/OT, rf_StationarTypeID, ... + * Признак отказа = сам факт наличия строки с rf_MedicalHistoryID + * (по причинам/типам не фильтруем). + */ +class MisDenial extends MaterializedViewModel +{ + protected $table = 'stt_denial'; // TODO: подставить реальное имя таблицы в реплике + protected $primaryKey = 'DenialID'; + + private static ?bool $tableExists = null; + + public function medicalHistory() + { + return $this->belongsTo(MedicalHistory::class, 'rf_MedicalHistoryID', 'id'); + } + + /** + * Доступна ли таблица отказов в текущей БД (реплике). + * Мемоизируем на процесс, чтобы не дёргать information_schema на каждый запрос. + */ + public static function tableAvailable(): bool + { + return self::$tableExists ??= Schema::hasTable((new self)->getTable()); + } +} diff --git a/app/Services/MedicalHistoryService.php b/app/Services/MedicalHistoryService.php index d0ca194..04e6f73 100644 --- a/app/Services/MedicalHistoryService.php +++ b/app/Services/MedicalHistoryService.php @@ -35,6 +35,7 @@ class MedicalHistoryService // 1. Один запрос: получаем "сырые" данные (без вычисляемых статусов) $all = MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', $periodMigrationFilter) ->when($search, function ($query, $search) { // Поиск по ФИО (точное совпадение или LIKE) @@ -143,7 +144,8 @@ class MedicalHistoryService { $query = MedicalHistory::query(); - $query->where('recipient_date', '>=', $dateRange->startSql()) + $query->withoutDenials() + ->where('recipient_date', '>=', $dateRange->startSql()) ->where('recipient_date', '<', $dateRange->endSql()) // 1. Оставляем только тех пациентов, у которых БЫЛО движение в этом отделении ->whereHas('latestMigration', fn($q) => $q->where('department_id', $departmentId)) @@ -160,7 +162,8 @@ class MedicalHistoryService { $query = MedicalHistory::query(); - $query->where('recipient_date', '>=', $dateRange->startSql()) + $query->withoutDenials() + ->where('recipient_date', '>=', $dateRange->startSql()) ->where('recipient_date', '<', $dateRange->endSql()) ->urgency($urgencyId) ->whereHas('migrations', function ($m) use ($departmentId) { @@ -179,6 +182,7 @@ class MedicalHistoryService public function getDepartmentHistories(DateRange $dateRange, int $departmentId) { return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->currentOrAdmitted($dateRange); }) @@ -194,6 +198,7 @@ class MedicalHistoryService public function getPlannedHistories(DateRange $dateRange, int $departmentId) { return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->currentOrAdmitted($dateRange); }) @@ -211,6 +216,7 @@ class MedicalHistoryService public function getEmergencyHistories(DateRange $dateRange, int $departmentId) { return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->currentOrAdmitted($dateRange); }) @@ -234,6 +240,7 @@ class MedicalHistoryService $now = Carbon::now(); return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->admitted($dateRange->startSql(), $dateRange->endSql()); }) @@ -246,6 +253,7 @@ class MedicalHistoryService public function getDischargedHistories(DateRange $dateRange, int $departmentId) { return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->discharged($dateRange->startSql(), $dateRange->endSql()); }) @@ -258,6 +266,7 @@ class MedicalHistoryService public function getDeceasedHistories(DateRange $dateRange, int $departmentId) { return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->deceased($dateRange->startSql(), $dateRange->endSql()); }) @@ -270,6 +279,7 @@ class MedicalHistoryService public function getTransferredHistories(DateRange $dateRange, int $departmentId) { return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->transferred($dateRange->startSql(), $dateRange->endSql()); }) @@ -282,6 +292,7 @@ class MedicalHistoryService public function getReanimationHistories(DateRange $dateRange, int $departmentId) { return MedicalHistory::query() + ->withoutDenials() ->whereHas('migrations', function ($q) use ($departmentId, $dateRange) { $q->department($departmentId)->currentOrAdmitted($dateRange); }) diff --git a/app/Services/MetricCalculators/PlanCalculator.php b/app/Services/MetricCalculators/PlanCalculator.php index ff45070..c7b65f8 100644 --- a/app/Services/MetricCalculators/PlanCalculator.php +++ b/app/Services/MetricCalculators/PlanCalculator.php @@ -32,6 +32,12 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf $startCurrentMonth = $endDate->copy()->startOfMonth()->setHours(9); $currentMonth = $endDate->month; + // Кол-во календарных месяцев, затронутых выбранным диапазоном (для нормы за период) + $monthsInRange = max( + 1, + ($endDate->year * 12 + $endDate->month) - ($startDate->year * 12 + $startDate->month) + 1 + ); + // Получаем фактические выписки с начала года по прошлый месяц $previousOutcomeMonth = DB::table('report_duties as r') ->join('duty_report_metric_results as mr', 'r.id', '=', 'mr.rf_report_id') @@ -99,6 +105,7 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf $results[$departmentId] = [ 'year_plan' => $annualPlan, 'month_plan' => $oneMonthPlan, + 'period_plan' => $oneMonthPlan * $monthsInRange, // норма за выбранный период (мес. × кол-во месяцев) 'total_debt' => $totalDebt, 'current_mouth_dept' => $currentMonthPlanOnly, 'cumulative_plan' => $cumulativePlan,