Обновлен стартовый экран

Переписаны запросы для статистики, отчетов
Добавлена интеграция отчета сестры
This commit is contained in:
brusnitsyn
2026-05-28 22:10:00 +09:00
parent 90e0d04dfd
commit 739168d427
96 changed files with 6663 additions and 1465 deletions

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DutyReportMetricResult extends Model
{
protected $primaryKey = 'metrika_result_id';
public $timestamps = false;
protected $fillable = [
'rf_metrika_item_id',
'rf_report_id',
'value',
];
public function report(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(ReportDuty::class, 'rf_report_id', 'id');
}
public function metrikaItem()
{
return $this->belongsTo(MetrikaItem::class, 'rf_metrika_item_id', 'metrika_item_id');
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DutyUnwantedEvent extends Model
{
protected $fillable = [
'report_duty_id',
'title',
'comment',
];
public function reportDuty()
{
return $this->belongsTo(ReportDuty::class, 'report_duty_id');
}
}

View File

@@ -33,6 +33,7 @@ class MedicalHistory extends MaterializedViewModel
public function latestMigration()
{
return $this->hasOne(MigrationPatient::class, 'medical_history_id', 'id')
->whereNotNull('out_date')
->latest('ingoing_date');
}
@@ -41,6 +42,17 @@ class MedicalHistory extends MaterializedViewModel
return $this->hasMany(Reanimation::class, 'medical_history_id', 'id');
}
public function observables()
{
return $this->hasMany(ObservableMedicalHistory::class, 'original_id', 'id');
}
public function observable()
{
return $this->hasOne(ObservableMedicalHistory::class, 'original_id', 'id')
->latest('observable_in');
}
public function operationsInDepartment($query, $departmentId)
{
return $this->operations()->where('department_id', $departmentId);

View File

@@ -116,11 +116,10 @@ class MigrationPatient extends MaterializedViewModel
{
return $query->where('medical_history_id', $historyId)
->department($departmentId)
->orderBy('ingoing_date', 'desc')
->limit(1)->first();
->orderBy('ingoing_date', 'desc');
}
public function getAdmittedInCurrentAttribute(): bool
public function getAdmittedInCurrentAttribute(DateRange $dateRange): bool
{
// Получаем дату поступления из последнего движения
$ingoing = $this->ingoing_date;
@@ -130,7 +129,7 @@ class MigrationPatient extends MaterializedViewModel
}
$ingoingLocal = Carbon::parse($ingoing)->setTimezone(config('app.timezone', 'Europe/Moscow'));
$now = Carbon::now(config('app.timezone', 'Europe/Moscow'));
$now = $dateRange->endDate;//Carbon::now(config('app.timezone', 'Europe/Moscow'));
// Окно смены: вчера 09:00 → сегодня 09:00
$shiftStart = $now->copy()->subDay()->setTime(9, 0);

View File

@@ -43,7 +43,6 @@ class MigrationPatientNurse extends Model
{
return $query->where('medical_history_id', $historyId)
->department($departmentId)
->orderBy('ingoing_date', 'desc')
->limit(1)->first();
->orderBy('ingoing_date', 'desc');
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ObservableMedicalHistory extends Model
{
protected $fillable = [
'source_type',
'original_id',
'observable_in',
'observable_out',
'observable_reason',
'out_reason',
'medical_card_number',
'full_name',
'birth_date',
'recipient_date',
'extract_date',
'death_date',
'male',
'urgency_id',
'hospital_result_id',
'visit_result_id',
'comment',
'user_id',
];
public function medicalHistory(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(ReportDutyPatient::class, 'original_id', 'original_id');
}
}

View File

@@ -39,4 +39,22 @@ class ReportDuty extends Model
{
return $this->belongsTo(ReportStatus::class);
}
public function doctor()
{
return $this->belongsTo(MisLpuDoctor::class, 'rf_lpudoctor_id', 'LPUDoctorID');
}
public function unwantedEvents()
{
return $this->hasMany(DutyUnwantedEvent::class, 'report_duty_id', 'id');
}
public function getLoadedDepartmentAttribute(int $patientsInDepartment)
{
$beds = DutyReportMetricResult::where('rf_report_id', $this->id)
->where('rf_metrika_item_id', 1)->pluck('value')->first() ?? 1;
return round((($patientsInDepartment ?? 0) * 100) / $beds);
}
}

View File

@@ -4,11 +4,13 @@ namespace App\Models;
use App\Services\DateRange;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class ReportDutyMigrationPatient extends Model
{
protected $fillable = [
'original_id',
'medical_history_id',
'ingoing_date',
'out_date',
@@ -32,12 +34,18 @@ class ReportDutyMigrationPatient extends Model
public function medicalHistory()
{
return $this->belongsTo(ReportNursePatient::class, 'medical_history_id', 'id');
return $this->belongsTo(ReportDutyPatient::class, 'medical_history_id', 'id');
}
public function operations()
{
return $this->hasMany(SurgicalOperation::class, 'migration_patient_id', 'id');
return $this->hasMany(SurgicalOperation::class, 'migration_patient_id', 'original_id');
}
public function reanimations()
{
return $this->hasMany(Reanimation::class, 'migration_patient_id', 'original_id')
->orderBy('out_date', 'desc');
}
// Пересечение с отчетным периодом
@@ -105,4 +113,23 @@ class ReportDutyMigrationPatient extends Model
->orderBy('ingoing_date', 'desc')
->limit(1)->first();
}
public function getAdmittedInCurrentAttribute(DateRange $dateRange): bool
{
// Получаем дату поступления из последнего движения
$ingoing = $this->ingoing_date;
if (!$ingoing) {
return false;
}
$ingoingLocal = Carbon::parse($ingoing)->setTimezone(config('app.timezone', 'Europe/Moscow'));
$now = $dateRange->endDate;//Carbon::now(config('app.timezone', 'Europe/Moscow'));
// Окно смены: вчера 09:00 → сегодня 09:00
$shiftStart = $now->copy()->subDay()->setTime(9, 0);
$shiftEnd = $now->copy()->setTime(9, 0);
return $ingoingLocal->between($shiftStart, $shiftEnd);
}
}

View File

@@ -39,13 +39,24 @@ class ReportDutyPatient extends Model
public function operations(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(SurgicalOperation::class, 'medical_history_id', 'id');
return $this->hasMany(SurgicalOperation::class, 'medical_history_id', 'original_id');
}
public function latestMigration()
{
return $this->hasOne(ReportDutyMigrationPatient::class, 'medical_history_id', 'id')
->latest('ingoing_date');
->orderBy('ingoing_date', 'desc');
}
public function observables()
{
return $this->hasMany(ObservableMedicalHistory::class, 'original_id', 'original_id');
}
public function observable()
{
return $this->hasOne(ObservableMedicalHistory::class, 'original_id', 'original_id')
->orderBy('observable_in', 'desc');
}
public function operationsInDepartment($query, $departmentId)

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Models;
use App\Services\DateRange;
use Illuminate\Database\Eloquent\Model;
class ReportDutyReanimation extends Model
{
protected $fillable = [
'original_id',
'migration_patient_id',
'medical_history_id',
'in_date',
'out_date',
'description',
'comment',
'stationar_branch_id',
'migration_stationar_branch_id',
'migration_department_id',
'doctor_id',
'user_id',
'mis_user_id',
];
public function medicalHistory()
{
return $this->belongsTo(ReportDutyPatient::class, 'medical_history_id', 'id');
}
public function migration()
{
return $this->belongsTo(ReportDutyMigrationPatient::class, 'migration_patient_id', 'id');
}
// Фильтр по подразделению
public function scopeDepartment($query, int $departmentId)
{
return $query->where('migration_department_id', $departmentId);
}
public function scopeCurrentOrAdmitted($query, DateRange $dateRange)
{
return $query->where(function ($q) use ($dateRange) {
// Вариант А: Пациент уже лежит (текущий)
$q->whereNull('out_date')
->whereNotNull('medical_history_id')
->where('in_date', '<', $dateRange->startSql());
})
->orWhere(function ($q) use ($dateRange) {
$q->where('in_date', '<=', $dateRange->endSql())
->where('in_date', '>', $dateRange->startSql());
});
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use App\Services\DateRange;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class ReportNurseMigrationPatient extends Model
@@ -105,4 +106,23 @@ class ReportNurseMigrationPatient extends Model
->orderBy('ingoing_date', 'desc')
->limit(1)->first();
}
public function getAdmittedInCurrentAttribute(DateRange $dateRange): bool
{
// Получаем дату поступления из последнего движения
$ingoing = $this->ingoing_date;
if (!$ingoing) {
return false;
}
$ingoingLocal = Carbon::parse($ingoing)->setTimezone(config('app.timezone', 'Europe/Moscow'));
$now = $dateRange->endDate;//Carbon::now(config('app.timezone', 'Europe/Moscow'));
// Окно смены: вчера 09:00 → сегодня 09:00
$shiftStart = $now->copy()->subDay()->setTime(9, 0);
$shiftEnd = $now->copy()->setTime(9, 0);
return $ingoingLocal->between($shiftStart, $shiftEnd);
}
}

View File

@@ -91,7 +91,7 @@ class UnifiedMigrationPatient extends Model
->limit(1)->first();
}
public function getAdmittedInCurrentAttribute(): bool
public function getAdmittedInCurrentAttribute(DateRange $dateRange): bool
{
// Получаем дату поступления из последнего движения
$ingoing = $this->ingoing_date;
@@ -101,7 +101,7 @@ class UnifiedMigrationPatient extends Model
}
$ingoingLocal = Carbon::parse($ingoing)->setTimezone(config('app.timezone', 'Europe/Moscow'));
$now = Carbon::now(config('app.timezone', 'Europe/Moscow'));
$now = $dateRange->endDate;//Carbon::now(config('app.timezone', 'Europe/Moscow'));
// Окно смены: вчера 09:00 → сегодня 09:00
$shiftStart = $now->copy()->subDay()->setTime(9, 0);

View File

@@ -26,8 +26,10 @@ class User extends Authenticatable
*/
protected $fillable = [
'name',
'login',
'email',
'password',
'is_active',
'rf_lpudoctor_id',
'rf_department_id',
'current_role_id',
@@ -76,7 +78,8 @@ class User extends Authenticatable
return $this->hasMany(UserRole::class, 'rf_user_id', 'id');
}
public function roles(): HasManyThrough
// Переименовано из roles() чтобы не конфликтовать с Spatie HasRoles::roles()
public function appRoles(): HasManyThrough
{
return $this->hasManyThrough(
Role::class,
@@ -90,7 +93,7 @@ class User extends Authenticatable
public function currentRole()
{
$defaultRoleId = $this->roles()->where('is_default', true)->first()->role_id;
$defaultRoleId = $this->appRoles()->where('is_default', true)->first()->role_id;
if (app()->runningInConsole()) {
// Код выполняется в CLI (команда artisan, тесты и т.д.)
@@ -128,19 +131,47 @@ class User extends Authenticatable
}
// Методы для проверки ролей
public function isAdmin()
public function isAdmin(): bool
{
return $this->currentRole()->slug === 'admin';
}
public function isDoctor()
public function isChiefDoctor(): bool
{
return $this->currentRole()->slug === 'doctor';
return $this->currentRole()->slug === 'gv';
}
public function isHeadOfDepartment()
public function isDeputyChief(): bool
{
return $this->currentRole()->slug === 'head_of_department';
return $this->currentRole()->slug === 'zam';
}
public function isHeadOfDepartment(): bool
{
return $this->currentRole()->slug === 'zav';
}
public function isDoctor(): bool
{
return $this->currentRole()->slug === 'dej';
}
public function isNurse(): bool
{
return $this->currentRole()->slug === 'nurse';
}
public function isSeniorStaff(): bool
{
return $this->isAdmin() || $this->isChiefDoctor() || $this->isDeputyChief() || $this->isHeadOfDepartment();
}
public function currentRoleCan(string $permission): bool
{
$slug = $this->currentRole()->slug;
return \Spatie\Permission\Models\Role::findByName($slug)
?->permissions->pluck('name')
->contains($permission) ?? false;
}
public function lpuDoctor()
@@ -153,29 +184,12 @@ class User extends Authenticatable
{
$departments = Department::all();
if ($this->isAdmin()) {
if ($this->isSeniorStaff()) {
return $departments;
}
return $this->department ? [$this->department] : [];
}
// Получение доступных действий
public function permissions()
{
$permissions = [
'view_dashboard' => true,
'view_metrics' => true,
'view_reports' => true,
];
if ($this->isAdmin() || $this->isDoctor() || $this->isHeadOfDepartment()) {
$permissions['create_metrics'] = true;
$permissions['edit_metrics'] = true;
$permissions['delete_metrics'] = true;
$permissions['manage_users'] = $this->isAdmin();
}
return $permissions;
}
}

View File

@@ -8,6 +8,8 @@ class UserDepartment extends Model
{
public $timestamps = false;
protected $primaryKey = 'user_department_id';
protected $fillable = [
'rf_user_id',
'rf_department_id',