* добавил объединение изменений движений
* добавил автоматическое создание движения при редактировании * добавил функционал для сохранения отчета и пациентов * изменил форматирование дат * добавил частичную перезагрузку при сохранении изменений
This commit is contained in:
@@ -6,8 +6,11 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\MedicalHistory;
|
||||
use App\Models\MedicalHistoryCorrection;
|
||||
use App\Models\MedicalHistoryNurse;
|
||||
use App\Models\MigrationPatient;
|
||||
use App\Models\MigrationPatientCorrection;
|
||||
use App\Models\UnifiedMedicalHistory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NurseController extends Controller
|
||||
{
|
||||
@@ -77,10 +80,33 @@ class NurseController extends Controller
|
||||
$data['medical_history_id'] = $id;
|
||||
$data['user_id'] = auth()->user()->id;
|
||||
|
||||
$result = MedicalHistoryCorrection::create($data);
|
||||
$currentMigration = MigrationPatient::currentMigration($id);
|
||||
|
||||
return response()->json([
|
||||
'data' => $result,
|
||||
], 201);
|
||||
$migrationData = [
|
||||
'migration_patient_id' => $currentMigration->id,
|
||||
'medical_history_id' => $id,
|
||||
'ingoing_date' => $data['recipient_date'],
|
||||
'out_date' => $data['extract_date'],
|
||||
'visit_result_id' => $data['visit_result_id'],
|
||||
'user_id' => $data['user_id'],
|
||||
];
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
$historyCorrection = MedicalHistoryCorrection::create($data);
|
||||
$migrationCorrection = MigrationPatientCorrection::create($migrationData);
|
||||
|
||||
if ($historyCorrection && $migrationCorrection) {
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'data' => $historyCorrection,
|
||||
], 201);
|
||||
} else {
|
||||
DB::rollBack();
|
||||
return response()->json([
|
||||
'data' => 'Something went wrong',
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Web;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Department;
|
||||
use App\Services\DateRangeService;
|
||||
use App\Services\NurseReportService;
|
||||
use App\Services\UnifiedMedicalHistoryService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -14,7 +15,8 @@ class NurseReportController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected DateRangeService $dateRangeService,
|
||||
protected UnifiedMedicalHistoryService $unifiedMedicalHistoryService
|
||||
protected UnifiedMedicalHistoryService $unifiedMedicalHistoryService,
|
||||
protected NurseReportService $nurseReportService,
|
||||
)
|
||||
{}
|
||||
|
||||
@@ -51,10 +53,16 @@ class NurseReportController extends Controller
|
||||
|
||||
/**
|
||||
* Сохранение отчета от роли мед. сестра
|
||||
* @return void
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store()
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
|
||||
$report = $this->nurseReportService->saveReport($dateRange);
|
||||
$this->nurseReportService->saveSnapshot($dateRange, $report);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class MigrationPatient extends MaterializedViewModel
|
||||
{
|
||||
return $query->where('out_date', '>', $from)
|
||||
->where('out_date', '<=', $to)
|
||||
->whereNotIn('stat_cure_result_id', [3, 4, 7])
|
||||
->whereNotIn('visit_result_id', [3, 4, 5, 6])
|
||||
->whereNull('death_date'); // умершие не считаются "выбывшими домой"
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class MigrationPatient extends MaterializedViewModel
|
||||
{
|
||||
return $query->where('out_date', '>', $from)
|
||||
->where('out_date', '<=', $to)
|
||||
->whereIn('stat_cure_result_id', [3, 4, 7]);
|
||||
->whereIn('visit_result_id', [3, 4]);
|
||||
}
|
||||
|
||||
public function scopeDeceased($query, string $from, string $to)
|
||||
@@ -89,17 +89,13 @@ class MigrationPatient extends MaterializedViewModel
|
||||
|
||||
public function scopeCurrent($query, DateRange $dateRange)
|
||||
{
|
||||
// return $query->where('ingoing_date', '<', $dateRange->startSql())
|
||||
// ->where(function ($q) use ($dateRange) {
|
||||
// $q->whereNull('out_date')->orWhere('out_date', '>', $dateRange->endSql());
|
||||
// });
|
||||
// return $query->where('ingoing_date', '<=', $dateRange->endSql())
|
||||
// ->has('medicalHistory')
|
||||
// ->where(function ($q) use ($dateRange) {
|
||||
// $q->whereNull('out_date')
|
||||
// ->orWhere('out_date', '>=', $dateRange->startSql())
|
||||
// ->where('out_date', '<=', $dateRange->endSql());
|
||||
// });
|
||||
return $query->where('is_actually_current', true);
|
||||
}
|
||||
|
||||
public function scopeCurrentMigration($query, $historyId)
|
||||
{
|
||||
return $query->where('medical_history_id', $historyId)
|
||||
->orderBy('ingoing_date', 'desc')
|
||||
->limit(1)->first();
|
||||
}
|
||||
}
|
||||
|
||||
31
app/Models/MigrationPatientCorrection.php
Normal file
31
app/Models/MigrationPatientCorrection.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MigrationPatientCorrection extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'migration_patient_id',
|
||||
'medical_history_id',
|
||||
'ingoing_date',
|
||||
'out_date',
|
||||
'diagnosis_id',
|
||||
'diagnosis_code',
|
||||
'diagnosis_name',
|
||||
'interrupted_event_id',
|
||||
'stationar_branch_id',
|
||||
'department_id',
|
||||
'visit_result_id',
|
||||
'stat_cure_result_id',
|
||||
'user_id',
|
||||
'mis_user_id',
|
||||
'comment',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ingoing_date' => 'datetime:UTC',
|
||||
'out_date' => 'datetime:UTC',
|
||||
];
|
||||
}
|
||||
25
app/Models/MigrationPatientNurse.php
Normal file
25
app/Models/MigrationPatientNurse.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MigrationPatientNurse extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'medical_history_id',
|
||||
'ingoing_date',
|
||||
'out_date',
|
||||
'diagnosis_id',
|
||||
'diagnosis_code',
|
||||
'diagnosis_name',
|
||||
'interrupted_event_id',
|
||||
'stationar_branch_id',
|
||||
'department_id',
|
||||
'visit_result_id',
|
||||
'stat_cure_result_id',
|
||||
'user_id',
|
||||
'mis_user_id',
|
||||
'comment',
|
||||
];
|
||||
}
|
||||
@@ -7,14 +7,29 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class ReportNurse extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'date_report',
|
||||
'report_date',
|
||||
'sent_at',
|
||||
'period_type',
|
||||
'period_start',
|
||||
'period_end',
|
||||
'status_id'
|
||||
'status_id',
|
||||
'rf_lpudoctor_id',
|
||||
'rf_department_id',
|
||||
'rf_user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'report_date' => 'date:Y-m-d',
|
||||
'sent_at' => 'datetime:Y-m-d H:i:s',
|
||||
'period_start' => 'datetime:Y-m-d H:i:s',
|
||||
'period_end' => 'datetime:Y-m-d H:i:s',
|
||||
];
|
||||
|
||||
public function department()
|
||||
{
|
||||
return $this->belongsTo(Department::class, 'rf_department_id', 'department_id');
|
||||
}
|
||||
|
||||
public function patients()
|
||||
{
|
||||
return $this->hasMany(ReportNursePatient::class, 'report_nurse_id');
|
||||
|
||||
34
app/Models/ReportNursePatient.php
Normal file
34
app/Models/ReportNursePatient.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ReportNursePatient extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'report_nurse_id',
|
||||
'source_type',
|
||||
'original_id',
|
||||
'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'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'birth_date' => 'date',
|
||||
'recipient_date' => 'datetime',
|
||||
'extract_date' => 'datetime',
|
||||
'death_date' => 'datetime',
|
||||
'male' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -10,16 +10,16 @@ class UnifiedMedicalHistory extends MaterializedViewModel
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
protected $casts = [
|
||||
'birth_date' => 'date',
|
||||
'recipient_date' => 'datetime',
|
||||
'extract_date' => 'datetime',
|
||||
'death_date' => 'datetime',
|
||||
'birth_date' => 'date:Y-m-d',
|
||||
'recipient_date' => 'datetime:Y-m-d H:i:s',
|
||||
'extract_date' => 'datetime:Y-m-d H:i:s',
|
||||
'death_date' => 'datetime:Y-m-d H:i:s',
|
||||
'male' => 'boolean',
|
||||
];
|
||||
|
||||
public function migrations(): \Illuminate\Database\Eloquent\Relations\HasMany|MedicalHistory
|
||||
{
|
||||
return $this->hasMany(MigrationPatient::class, 'medical_history_id', 'id');
|
||||
return $this->hasMany(UnifiedMigrationPatient::class, 'medical_history_id', 'id');
|
||||
}
|
||||
|
||||
public function operations(): \Illuminate\Database\Eloquent\Relations\HasMany|MedicalHistory
|
||||
@@ -29,7 +29,7 @@ class UnifiedMedicalHistory extends MaterializedViewModel
|
||||
|
||||
public function latestMigration()
|
||||
{
|
||||
return $this->hasOne(MigrationPatient::class, 'medical_history_id', 'id')
|
||||
return $this->hasOne(UnifiedMigrationPatient::class, 'medical_history_id', 'id')
|
||||
->latest('ingoing_date');
|
||||
}
|
||||
|
||||
|
||||
110
app/Models/UnifiedMigrationPatient.php
Normal file
110
app/Models/UnifiedMigrationPatient.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\DateRange;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UnifiedMigrationPatient extends Model
|
||||
{
|
||||
protected $table = 'v_unified_migration_patient_nurse';
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
protected $casts = [
|
||||
'ingoing_date' => 'datetime:Y-m-d H:i:s',
|
||||
'out_date' => 'datetime:Y-m-d H:i:s',
|
||||
];
|
||||
|
||||
public function medicalHistory()
|
||||
{
|
||||
return $this->belongsTo(UnifiedMedicalHistory::class, 'medical_history_id', 'id');
|
||||
}
|
||||
|
||||
public function operations()
|
||||
{
|
||||
return $this->hasMany(SurgicalOperation::class, 'migration_patient_id', 'id');
|
||||
}
|
||||
|
||||
// Пересечение с отчетным периодом
|
||||
public function scopeDateRange($query, string $from, string $to)
|
||||
{
|
||||
return $query->where('ingoing_date', '<=', $to)
|
||||
->where(function ($q) use ($from) {
|
||||
$q->whereNull('out_date')->orWhere('out_date', '>=', $from);
|
||||
});
|
||||
}
|
||||
|
||||
// Фильтр по подразделению (получает ID отделений)
|
||||
public function scopeDepartment($query, int $departmentId)
|
||||
{
|
||||
$branchIds = DB::table('stt_stationarbranch')
|
||||
->where('rf_DepartmentID', $departmentId)
|
||||
->pluck('StationarBranchID');
|
||||
|
||||
return $query->whereIn('stationar_branch_id', $branchIds);
|
||||
}
|
||||
|
||||
// Добавляет вычисляемый столбец `category` (только для отображения)
|
||||
public function scopeWithCategory($query, string $from, string $to)
|
||||
{
|
||||
$sql = "CASE
|
||||
WHEN ingoing_date BETWEEN ? AND ? THEN 'admitted'
|
||||
WHEN out_date BETWEEN ? AND ? THEN
|
||||
CASE
|
||||
WHEN death_date IS NOT NULL AND death_date BETWEEN ? AND ? THEN 'deceased'
|
||||
WHEN stat_cure_result_id IN (3,4,7) THEN 'transferred'
|
||||
ELSE 'discharged'
|
||||
END
|
||||
WHEN ingoing_date < ? AND (out_date IS NULL OR out_date > ?) THEN 'current'
|
||||
ELSE 'historical'
|
||||
END as category";
|
||||
|
||||
return $query->selectRaw($sql, [$from, $to, $from, $to, $from, $to, $to, $to]);
|
||||
}
|
||||
|
||||
// Быстрые фильтры по статусам (используют индексы MV, а не computed column)
|
||||
public function scopeAdmitted($query, string $from, string $to)
|
||||
{
|
||||
return $query->where('ingoing_date', '>', $from)
|
||||
->where('ingoing_date', '<=', $to);
|
||||
}
|
||||
|
||||
public function scopeDischarged($query, string $from, string $to)
|
||||
{
|
||||
return $query->where('out_date', '>', $from)
|
||||
->where('out_date', '<=', $to)
|
||||
->whereNotIn('visit_result_id', [3, 4, 5, 6])
|
||||
->whereNull('death_date'); // умершие не считаются "выбывшими домой"
|
||||
}
|
||||
|
||||
public function scopeTransferred($query, string $from, string $to)
|
||||
{
|
||||
return $query->where('out_date', '>', $from)
|
||||
->where('out_date', '<=', $to)
|
||||
->whereIn('visit_result_id', [3, 4]);
|
||||
}
|
||||
|
||||
public function scopeDeceased($query, string $from, string $to)
|
||||
{
|
||||
return $query->whereNotNull('death_date')
|
||||
->where('death_date', '>', $from)
|
||||
->where('death_date', '<=', $to);
|
||||
}
|
||||
|
||||
public function scopeCurrent($query, DateRange $dateRange)
|
||||
{
|
||||
return $query->whereNull('out_date')
|
||||
->whereNotNull('medical_history_id')
|
||||
->whereHas('medicalHistory', function ($q) use ($dateRange) {
|
||||
$q->whereNull('extract_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeCurrentMigration($query, $historyId)
|
||||
{
|
||||
return $query->where('medical_history_id', $historyId)
|
||||
->orderBy('ingoing_date', 'desc')
|
||||
->limit(1)->first();
|
||||
}
|
||||
}
|
||||
@@ -55,8 +55,8 @@ class DateRangeService
|
||||
? $this->parseDate($startAtInput)
|
||||
: Carbon::now('Asia/Yakutsk');
|
||||
|
||||
$endDate = $endDate->copy()->setTime(7, 0);
|
||||
$startDate = $startDate->copy()->subDay()->setTime(7, 0);
|
||||
$endDate = $endDate->copy()->setTime(9, 0);
|
||||
$startDate = $startDate->copy()->subDay()->setTime(9, 0);
|
||||
|
||||
return new DateRange(
|
||||
startDate: $startDate,
|
||||
@@ -85,10 +85,10 @@ class DateRangeService
|
||||
$startDate = Carbon::now('Asia/Yakutsk')
|
||||
->startOfYear() // 1 января текущего года
|
||||
->subDay()
|
||||
->setTime(7, 0);
|
||||
->setTime(9, 0);
|
||||
|
||||
$endDate = Carbon::now('Asia/Yakutsk')
|
||||
->setTime(7, 0);
|
||||
->setTime(9, 0);
|
||||
|
||||
return [
|
||||
$startDate->format('Y-m-d H:i:s'),
|
||||
@@ -123,11 +123,11 @@ class DateRangeService
|
||||
$endDate = $this->parseDate($endAt);
|
||||
|
||||
if ($startDate->isSameDay($endDate)) {
|
||||
$startDate = $startDate->subDay()->setTime(7, 0);
|
||||
$endDate = $endDate->setTime(7, 0);
|
||||
$startDate = $startDate->subDay()->setTime(9, 0);
|
||||
$endDate = $endDate->setTime(9, 0);
|
||||
} else {
|
||||
$startDate = $startDate->setTime(7, 0);
|
||||
$endDate = $endDate->setTime(7, 0);
|
||||
$startDate = $startDate->setTime(9, 0);
|
||||
$endDate = $endDate->setTime(9, 0);
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -140,10 +140,10 @@ class DateRangeService
|
||||
{
|
||||
$startDate = Carbon::now('Asia/Yakutsk')
|
||||
->subDay()
|
||||
->setTime(7, 0);
|
||||
->setTime(9, 0);
|
||||
|
||||
$endDate = Carbon::now('Asia/Yakutsk')
|
||||
->setTime(7, 0);
|
||||
->setTime(9, 0);
|
||||
|
||||
return [
|
||||
$startDate->format('Y-m-d H:i:s'),
|
||||
@@ -197,8 +197,8 @@ class DateRangeService
|
||||
{
|
||||
// Для автоматического заполнения используем логику как для врача
|
||||
// (вчера 07:00 - сегодня 07:00)
|
||||
$startDate = $date->copy()->subDay()->setTime(7, 0);
|
||||
$endDate = $date->copy()->setTime(7, 0);
|
||||
$startDate = $date->copy()->subDay()->setTime(9, 0);
|
||||
$endDate = $date->copy()->setTime(9, 0);
|
||||
|
||||
return new DateRange(
|
||||
startDate: $startDate,
|
||||
|
||||
143
app/Services/NurseReportService.php
Normal file
143
app/Services/NurseReportService.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\ReportNurse;
|
||||
use App\Models\UnifiedMedicalHistory;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class NurseReportService
|
||||
{
|
||||
/**
|
||||
* Базовый запрос для всех отчётов
|
||||
* Фильтрует по отделению и периоду, подгружает связи
|
||||
*/
|
||||
protected function baseQuery(DateRange $dateRange, int $departmentId): Builder
|
||||
{
|
||||
return UnifiedMedicalHistory::query()
|
||||
->whereHas('migrations', function ($q) use ($departmentId, $dateRange) {
|
||||
$q->department($departmentId)->dateRange($dateRange->startSql(), $dateRange->endSql());
|
||||
})
|
||||
->with(['latestMigration' => function ($q) use ($departmentId, $dateRange) {
|
||||
$q->department($departmentId)->dateRange($dateRange->startSql(), $dateRange->endSql());
|
||||
}]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить отчет
|
||||
*/
|
||||
public function saveReport(DateRange $dateRange, ?int $userId = null, ?int $lpuDoctorId = null, ?int $departmentId = null)
|
||||
{
|
||||
$user = $userId ? User::find($userId) : auth()->user();
|
||||
|
||||
$lpuDoctorId = $lpuDoctorId ?? $user->rf_lpudoctor_id;
|
||||
$departmentId = $departmentId ?? $user->rf_department_id;
|
||||
|
||||
$data = [
|
||||
'report_date' => Carbon::now()->format('Y-m-d'),
|
||||
'sent_at' => Carbon::now()->format('Y-m-d H:i:s'),
|
||||
'period_type' => 'day',
|
||||
'period_start' => $dateRange->startSql(),
|
||||
'period_end' => $dateRange->endSql(),
|
||||
'status_id' => 2, // опубликован
|
||||
'rf_lpudoctor_id' => $lpuDoctorId,
|
||||
'rf_department_id' => $departmentId,
|
||||
'rf_user_id' => $user->id,
|
||||
];
|
||||
|
||||
$report = ReportNurse::create($data);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить снимок пациентов за период
|
||||
*/
|
||||
public function saveSnapshot(DateRange $dateRange, ReportNurse $reportNurse, ?int $departmentId = null, ?int $userId = null): array
|
||||
{
|
||||
$departmentId = $departmentId ?? $reportNurse->department->rf_mis_department_id;
|
||||
$userId = $userId ?? $reportNurse->rf_user_id;
|
||||
$startYear = Carbon::now()->startOfYear()->format('Y-m-d');
|
||||
|
||||
$query = UnifiedMedicalHistory::query()
|
||||
// Фильтруем движения по отделению + пересечение дат
|
||||
->whereHas('migrations', function ($q) use ($departmentId, $dateRange, $startYear) {
|
||||
$q->where('department_id', $departmentId)
|
||||
// пребывание пересекается с отчётным периодом
|
||||
->where('ingoing_date', '<=', $dateRange->endSql())
|
||||
->where('ingoing_date', '>=', $startYear)
|
||||
->where(function ($sub) use ($dateRange) {
|
||||
$sub->whereNull('out_date')
|
||||
->orWhere('out_date', '>=', $dateRange->startSql())
|
||||
->where('out_date', '<=', $dateRange->endSql());
|
||||
})
|
||||
->where(function ($sub) use ($dateRange) {
|
||||
$sub->whereNull('out_date')
|
||||
->orWhere('out_date', '>=', $dateRange->startSql())
|
||||
->where('out_date', '<=', $dateRange->endSql());
|
||||
});
|
||||
})
|
||||
// Подгружаем последнее движение для денормализации
|
||||
->with(['latestMigration' => function ($q) use ($departmentId, $dateRange, $startYear) {
|
||||
$q->where('department_id', $departmentId)
|
||||
->where('ingoing_date', '<=', $dateRange->endSql())
|
||||
->where('ingoing_date', '>=', $startYear)
|
||||
->where(function ($sub) use ($dateRange) {
|
||||
$sub->whereNull('out_date')
|
||||
->orWhere('out_date', '>=', $dateRange->startSql())
|
||||
->where('out_date', '<=', $dateRange->endSql());
|
||||
})
|
||||
->latest('ingoing_date'); // если несколько, берём последнее
|
||||
}]);
|
||||
|
||||
$rawSql = $query->toRawSql();
|
||||
|
||||
// Получаем данные (chunk для памяти, если пациентов > 1000)
|
||||
$patients = $query->cursor();
|
||||
|
||||
$savedCount = 0;
|
||||
$reportData = [];
|
||||
|
||||
foreach ($patients as $patient) {
|
||||
// Подготовка данных для сохранения (денормализация)
|
||||
$data = [
|
||||
'report_nurse_id' => $reportNurse->id,
|
||||
'source_type' => $patient->source_type,
|
||||
'original_id' => $patient->original_id,
|
||||
'medical_card_number' => $patient->medical_card_number,
|
||||
'full_name' => $patient->full_name,
|
||||
'birth_date' => $patient->birth_date,
|
||||
'recipient_date' => $patient->recipient_date,
|
||||
'extract_date' => $patient->extract_date,
|
||||
'death_date' => $patient->death_date,
|
||||
'male' => $patient->male,
|
||||
'urgency_id' => $patient->urgency_id,
|
||||
'hospital_result_id' => $patient->hospital_result_id,
|
||||
'visit_result_id' => $patient->visit_result_id,
|
||||
'comment' => $patient->comment,
|
||||
'user_id' => $userId,
|
||||
];
|
||||
|
||||
// UPSERT: обновляем если запись с таким ключом уже есть, иначе вставляем
|
||||
\DB::table('report_nurse_patients')->upsert(
|
||||
[$data],
|
||||
['report_nurse_id', 'source_type', 'original_id'], // уникальные ключи
|
||||
[
|
||||
'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'
|
||||
] // обновляемые поля
|
||||
);
|
||||
|
||||
$savedCount++;
|
||||
$reportData[] = $data;
|
||||
}
|
||||
|
||||
return [
|
||||
'saved_count' => $savedCount,
|
||||
'report_date' => $dateRange->startSql(),
|
||||
'department_id' => $departmentId,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ReportNurse;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('report_nurse_patients', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(ReportNurse::class, 'report_nurse_id');
|
||||
$table->string('source_type');
|
||||
$table->bigInteger('original_id');
|
||||
$table->string('medical_card_number')->nullable();
|
||||
$table->string('full_name');
|
||||
$table->date('birth_date')->nullable();
|
||||
$table->dateTime('recipient_date');
|
||||
$table->dateTime('extract_date')->nullable();
|
||||
$table->dateTime('death_date')->nullable();
|
||||
$table->boolean('male')->default(true);
|
||||
$table->integer('urgency_id')->nullable();
|
||||
$table->integer('hospital_result_id')->nullable();
|
||||
$table->integer('visit_result_id')->nullable();
|
||||
$table->text('comment')->nullable();
|
||||
$table->foreignIdFor(\App\Models\User::class, 'user_id');
|
||||
$table->unique(['report_nurse_id', 'source_type', 'original_id']); // защита от дублей
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('report_nurse_patients');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('migration_patient_corrections', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('migration_patient_id');
|
||||
$table->integer('medical_history_id');
|
||||
$table->dateTime('ingoing_date')->nullable();
|
||||
$table->dateTime('out_date')->nullable();
|
||||
$table->integer('diagnosis_id')->nullable();
|
||||
$table->string('diagnosis_code')->nullable();
|
||||
$table->string('diagnosis_name')->nullable();
|
||||
$table->integer('interrupted_event_id')->nullable();
|
||||
$table->integer('stationar_branch_id')->nullable();
|
||||
$table->integer('department_id')->nullable();
|
||||
$table->integer('visit_result_id')->nullable();
|
||||
$table->integer('stat_cure_result_id')->nullable();
|
||||
|
||||
$table->foreignIdFor(\App\Models\User::class, 'user_id');
|
||||
$table->integer('mis_user_id')->nullable();
|
||||
$table->text('comment')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('migration_patient_corrections');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('migration_patient_nurses', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor( \App\Models\MedicalHistoryNurse::class, 'medical_history_id');
|
||||
$table->dateTime('ingoing_date')->nullable();
|
||||
$table->dateTime('out_date')->nullable();
|
||||
$table->integer('diagnosis_id')->nullable();
|
||||
$table->string('diagnosis_code')->nullable();
|
||||
$table->string('diagnosis_name')->nullable();
|
||||
$table->integer('interrupted_event_id')->nullable();
|
||||
$table->integer('stationar_branch_id')->nullable();
|
||||
$table->integer('department_id')->nullable();
|
||||
$table->integer('visit_result_id')->nullable();
|
||||
$table->integer('stat_cure_result_id')->nullable();
|
||||
$table->foreignIdFor(\App\Models\User::class, 'user_id')->nullable();
|
||||
$table->integer('mis_user_id')->nullable();
|
||||
$table->text('comment')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('migration_patient_nurses');
|
||||
}
|
||||
};
|
||||
23
database/seeders/ReportStatusSeeder.php
Normal file
23
database/seeders/ReportStatusSeeder.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ReportStatus;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ReportStatusSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
ReportStatus::create([
|
||||
'name' => 'Черновик'
|
||||
]);
|
||||
ReportStatus::create([
|
||||
'name' => 'Опубликован'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,13 @@ import {computed, ref, watch} from "vue";
|
||||
import AppRadio from "../../../Components/AppRadio.vue";
|
||||
import {useDebounceFn} from "@vueuse/core";
|
||||
import {format} from "date-fns";
|
||||
import {router} from "@inertiajs/vue3";
|
||||
const show = defineModel('show', { default: false })
|
||||
|
||||
const currentStep = ref(1)
|
||||
const currentStatus = ref('process')
|
||||
const isFinallyStep = computed(() => currentStep.value === 2)
|
||||
const disableFillable = ref(false)
|
||||
const buttonLoading = ref(false)
|
||||
|
||||
const form = ref({
|
||||
patient_source: 'mis',
|
||||
@@ -61,7 +62,43 @@ const visitResultOptions = [
|
||||
{
|
||||
label: '1 - Выписан',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '2 - Переведён в др. ЛПУ',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '3 - Переведён в дневной стационар',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '4 - Переведён на другой профиль коек',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: '5 - Умер',
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
label: '6 - Умер в приёмном покое',
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: '7 - Лечение прервано по инициативе пациента',
|
||||
value: 7
|
||||
},
|
||||
{
|
||||
label: '8 - Лечение прервано по инициативе ЛПУ',
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
label: '9 - Лечение продолжено',
|
||||
value: 9
|
||||
},
|
||||
{
|
||||
label: '10 - Самовольно прерванное лечение',
|
||||
value: 10
|
||||
},
|
||||
]
|
||||
|
||||
const prev = () => {
|
||||
@@ -77,10 +114,25 @@ const next = () => {
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
buttonLoading.value = true
|
||||
axios.post('/api/nurse/patients', {
|
||||
...form.value
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
router.reload({
|
||||
only: [
|
||||
'inDepartmentHistories',
|
||||
'recipientHistories',
|
||||
'dischargedHistories',
|
||||
'deceasedHistories',
|
||||
'transferredHistories'
|
||||
],
|
||||
onSuccess: () => {
|
||||
show.value = false
|
||||
},
|
||||
onFinish: () => {
|
||||
buttonLoading.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,6 +177,8 @@ watch(() => currentStep.value, (val) => {
|
||||
title="Добавление пациента"
|
||||
class="max-w-xl min-h-[500px]"
|
||||
draggable
|
||||
:close-on-esc="!buttonLoading"
|
||||
:closable="!buttonLoading"
|
||||
>
|
||||
<NSteps size="small" :current="currentStep" :status="currentStatus">
|
||||
<NStep title="Тип пациента" description="Укажите тип пациента" />
|
||||
@@ -163,16 +217,16 @@ watch(() => currentStep.value, (val) => {
|
||||
<NSelect filterable v-model:value="form.visit_result_id" :options="visitResultOptions" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата рождения">
|
||||
<NDatePicker v-model:value="form.birth_date" class="w-full" value-format="yyyy-MM-dd" />
|
||||
<NDatePicker v-model:value="form.birth_date" class="w-full" format="dd.MM.yyyy" value-format="yyyy-MM-dd" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата и время госпитализации">
|
||||
<NDatePicker v-model:value="form.recipient_date" type="datetime" class="w-full" value-format="yyyy-MM-dd HH:mm:ss" />
|
||||
<NDatePicker v-model:value="form.recipient_date" type="datetime" class="w-full" format="dd.MM.yyyy HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата и время выписки">
|
||||
<NDatePicker v-model:value="form.extract_date" type="datetime" class="w-full" value-format="yyyy-MM-dd HH:mm:ss" />
|
||||
<NDatePicker v-model:value="form.extract_date" type="datetime" class="w-full" format="dd.MM.yyyy HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата и время смерти">
|
||||
<NDatePicker v-model:value="form.death_date" type="datetime" class="w-full" value-format="yyyy-MM-dd HH:mm:ss" />
|
||||
<NDatePicker v-model:value="form.death_date" type="datetime" class="w-full" format="dd.MM.yyyy HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" clearable />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NTabPane>
|
||||
@@ -180,13 +234,13 @@ watch(() => currentStep.value, (val) => {
|
||||
<template #action>
|
||||
<NGrid cols="2" justify="space-between">
|
||||
<NGi>
|
||||
<NButton v-if="currentStep > 1" secondary @click="prev">
|
||||
<NButton v-if="currentStep > 1" secondary @click="prev" :loading="buttonLoading">
|
||||
Назад
|
||||
</NButton>
|
||||
</NGi>
|
||||
<NGi>
|
||||
<NFlex justify="end">
|
||||
<NButton v-if="isFinallyStep" secondary type="primary" @click="submit">
|
||||
<NButton v-if="isFinallyStep" secondary type="primary" @click="submit" :loading="buttonLoading">
|
||||
Добавить
|
||||
</NButton>
|
||||
<NButton v-else secondary type="primary" @click="next">
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import {computed, ref, watch} from "vue";
|
||||
import AppRadio from "../../../Components/AppRadio.vue";
|
||||
import axios from "axios";
|
||||
import {router} from "@inertiajs/vue3";
|
||||
const show = defineModel('show', { default: false })
|
||||
const props = defineProps({
|
||||
historyId: {
|
||||
@@ -37,6 +38,7 @@ const form = ref({
|
||||
extract_date: null
|
||||
})
|
||||
const loading = ref(true)
|
||||
const buttonLoading = ref(false)
|
||||
|
||||
const urgencyOptions = [
|
||||
{
|
||||
@@ -60,14 +62,65 @@ const visitResultOptions = [
|
||||
{
|
||||
label: '1 - Выписан',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '2 - Переведён в др. ЛПУ',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '3 - Переведён в дневной стационар',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '4 - Переведён на другой профиль коек',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: '5 - Умер',
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
label: '6 - Умер в приёмном покое',
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: '7 - Лечение прервано по инициативе пациента',
|
||||
value: 7
|
||||
},
|
||||
{
|
||||
label: '8 - Лечение прервано по инициативе ЛПУ',
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
label: '9 - Лечение продолжено',
|
||||
value: 9
|
||||
},
|
||||
{
|
||||
label: '10 - Самовольно прерванное лечение',
|
||||
value: 10
|
||||
},
|
||||
]
|
||||
|
||||
const submit = () => {
|
||||
axios.post(`/api/nurse/patients/${form.value.patient_id}/correction`, {
|
||||
const submit = async () => {
|
||||
buttonLoading.value = true
|
||||
await axios.post(`/api/nurse/patients/${form.value.patient_id}/correction`, {
|
||||
...form.value
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
router.reload({
|
||||
only: [
|
||||
'inDepartmentHistories',
|
||||
'recipientHistories',
|
||||
'dischargedHistories',
|
||||
'deceasedHistories',
|
||||
'transferredHistories'
|
||||
],
|
||||
onSuccess: () => {
|
||||
show.value = false
|
||||
},
|
||||
onFinish: () => {
|
||||
buttonLoading.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,6 +155,7 @@ watch(() => props.historyId, async (newHistoryId, historyId) => {
|
||||
title="Редактирование пациента"
|
||||
class="max-w-xl min-h-[500px] relative"
|
||||
draggable
|
||||
:closable="!buttonLoading"
|
||||
>
|
||||
<div v-if="loading">
|
||||
<NSpin class="absolute top-1/2 left-1/2 -translate-x-1/2" />
|
||||
@@ -117,21 +171,21 @@ watch(() => props.historyId, async (newHistoryId, historyId) => {
|
||||
<NSelect filterable v-model:value="form.visit_result_id" :options="visitResultOptions" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата рождения">
|
||||
<NDatePicker v-model:value="form.birth_date" class="w-full" />
|
||||
<NDatePicker v-model:formatted-value="form.birth_date" class="w-full" format="dd.MM.yyyy" value-format="yyyy-MM-dd" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата и время госпитализации">
|
||||
<NDatePicker v-model:value="form.recipient_date" type="datetime" class="w-full" />
|
||||
<NDatePicker v-model:formatted-value="form.recipient_date" type="datetime" class="w-full" format="dd.MM.yyyy HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата и время выписки">
|
||||
<NDatePicker v-model:value="form.extract_date" type="datetime" class="w-full" />
|
||||
<NDatePicker v-model:formatted-value="form.extract_date" type="datetime" class="w-full" format="dd.MM.yyyy HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="1" label="Дата и время смерти">
|
||||
<NDatePicker v-model:value="form.death_date" type="datetime" class="w-full" />
|
||||
<NDatePicker v-model:formatted-value="form.death_date" type="datetime" class="w-full" format="dd.MM.yyyy HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" clearable />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
<template v-if="!loading" #action>
|
||||
<NFlex justify="end">
|
||||
<NButton secondary type="primary" @click="submit">
|
||||
<NButton secondary type="primary" @click="submit" :loading="buttonLoading">
|
||||
Сохранить
|
||||
</NButton>
|
||||
</NFlex>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {TbCirclePlus, TbPencil} from 'vue-icons-plus/tb'
|
||||
import {useAuthStore} from "../../../Stores/auth.js";
|
||||
import AddMedicalHistoryModal from "../Components/AddMedicalHistoryModal.vue";
|
||||
import EditMedicalHistoryModal from "../Components/EditMedicalHistoryModal.vue";
|
||||
import {router} from "@inertiajs/vue3";
|
||||
|
||||
const props = defineProps({
|
||||
inDepartmentHistories: {
|
||||
@@ -85,7 +86,11 @@ const onClickEditButton = (historyId) => {
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
// TODO: сохранение отчета и пациентов
|
||||
router.post('/nurse/report/save', {}, {
|
||||
onSuccess: () => {
|
||||
alert('Сохранено')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formattedLabel = (word, count) => {
|
||||
@@ -158,7 +163,7 @@ const formattedLabel = (word, count) => {
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</AppPanel>
|
||||
<NButton secondary size="large">
|
||||
<NButton secondary size="large" @click="submit">
|
||||
Сохранить отчет
|
||||
</NButton>
|
||||
</AppContainer>
|
||||
|
||||
Reference in New Issue
Block a user