Files
onboard/tests/Feature/AutoFillReportsTest.php
2026-04-24 16:46:10 +09:00

459 lines
17 KiB
PHP

<?php
use App\Console\Commands\FillReportsFromDate;
use App\Models\Department;
use App\Models\User;
use App\Services\AutoReportService;
use App\Services\DateRange;
use App\Services\DateRangeService;
use App\Services\PatientService;
use App\Services\ReportService;
use App\Services\SnapshotService;
use App\Services\UnifiedPatientService;
use Carbon\Carbon;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Symfony\Component\Console\Tester\CommandTester;
beforeEach(function () {
Carbon::setTestNow(Carbon::parse('2026-04-09 08:00:00', 'Asia/Yakutsk'));
foreach ([
'departments',
'roles',
'users',
'user_roles',
'user_departments',
'reports',
'metrika_results',
'medical_history_snapshots',
'department_patients',
'unwanted_events',
'observation_patients',
'stt_stationarbranch',
] as $table) {
Schema::dropIfExists($table);
}
Schema::create('departments', function (Blueprint $table) {
$table->id('department_id');
$table->string('name_short')->nullable();
$table->integer('rf_mis_department_id')->nullable();
$table->integer('rf_department_type')->nullable();
});
Schema::create('roles', function (Blueprint $table) {
$table->id('role_id');
$table->string('name')->nullable();
$table->string('slug');
$table->boolean('is_active')->default(true);
});
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('login')->unique();
$table->string('password');
$table->boolean('is_active')->default(true);
$table->unsignedBigInteger('rf_lpudoctor_id')->nullable();
$table->unsignedBigInteger('rf_department_id')->nullable();
$table->unsignedBigInteger('current_role_id')->nullable();
$table->rememberToken()->nullable();
$table->timestamps();
});
Schema::create('user_roles', function (Blueprint $table) {
$table->id('user_role_id');
$table->unsignedBigInteger('rf_user_id');
$table->unsignedBigInteger('rf_role_id');
$table->boolean('is_active')->default(true);
$table->boolean('is_default')->default(false);
});
Schema::create('user_departments', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('rf_user_id');
$table->unsignedBigInteger('rf_department_id');
$table->boolean('is_favorite')->default(false);
$table->integer('order')->default(0);
$table->string('user_name')->nullable();
});
Schema::create('reports', function (Blueprint $table) {
$table->id('report_id');
$table->date('created_at');
$table->dateTime('sent_at')->nullable();
$table->unsignedBigInteger('rf_department_id');
$table->unsignedBigInteger('rf_user_id')->nullable();
$table->unsignedBigInteger('rf_lpudoctor_id')->nullable();
});
Schema::create('metrika_results', function (Blueprint $table) {
$table->id('metrika_result_id');
$table->unsignedBigInteger('rf_report_id');
$table->unsignedBigInteger('rf_metrika_item_id');
$table->string('value')->nullable();
});
Schema::create('medical_history_snapshots', function (Blueprint $table) {
$table->id('medical_history_snapshot_id');
$table->unsignedBigInteger('rf_report_id');
$table->unsignedBigInteger('rf_medicalhistory_id')->nullable();
$table->unsignedBigInteger('rf_department_patient_id')->nullable();
$table->string('patient_type');
$table->string('patient_uid')->nullable();
$table->string('patient_source_type')->nullable();
$table->string('patient_kind')->nullable();
$table->string('full_name')->nullable();
$table->date('birth_date')->nullable();
$table->string('diagnosis_code')->nullable();
$table->string('diagnosis_name')->nullable();
$table->dateTime('admitted_at')->nullable();
$table->string('outcome_type')->nullable();
$table->dateTime('outcome_at')->nullable();
$table->boolean('is_manual')->default(false);
$table->timestamps();
});
Schema::create('department_patients', function (Blueprint $table) {
$table->id('department_patient_id');
$table->unsignedBigInteger('rf_department_id');
$table->string('source_type')->default('manual');
$table->unsignedBigInteger('rf_medicalhistory_id')->nullable();
$table->string('full_name');
$table->date('birth_date')->nullable();
$table->string('patient_kind')->nullable();
$table->string('diagnosis_code')->nullable();
$table->string('diagnosis_name')->nullable();
$table->dateTime('admitted_at')->nullable();
$table->boolean('is_current')->default(true);
$table->string('outcome_type')->nullable();
$table->dateTime('outcome_at')->nullable();
$table->unsignedBigInteger('created_by')->nullable();
$table->dateTime('linked_to_mis_at')->nullable();
$table->timestamps();
});
Schema::create('unwanted_events', function (Blueprint $table) {
$table->id('unwanted_event_id');
$table->unsignedBigInteger('rf_report_id')->nullable();
});
Schema::create('observation_patients', function (Blueprint $table) {
$table->id('observation_patient_id');
$table->unsignedBigInteger('rf_report_id')->nullable();
$table->unsignedBigInteger('rf_department_id')->nullable();
$table->unsignedBigInteger('rf_medicalhistory_id')->nullable();
$table->unsignedBigInteger('rf_department_patient_id')->nullable();
});
Schema::create('stt_stationarbranch', function (Blueprint $table) {
$table->integer('StationarBranchID')->primary();
$table->integer('rf_DepartmentID');
});
});
afterEach(function () {
Carbon::setTestNow();
\Mockery::close();
});
function autoFillRange(): DateRange
{
return new DateRange(
startDate: Carbon::parse('2026-04-08 06:00:00', 'Asia/Yakutsk'),
endDate: Carbon::parse('2026-04-09 06:00:00', 'Asia/Yakutsk'),
startDateRaw: '2026-04-08 06:00:00',
endDateRaw: '2026-04-09 06:00:00',
isOneDay: true,
);
}
it('builds auto fill payload from the same patient metrics that are stored in reports', function () {
DB::table('stt_stationarbranch')->insert([
'StationarBranchID' => 10,
'rf_DepartmentID' => 100,
]);
$department = new Department;
$department->department_id = 10;
$department->rf_mis_department_id = 100;
$user = new class extends User
{
public function isHeadOfDepartment()
{
return false;
}
public function isAdmin()
{
return false;
}
};
$user->id = 15;
$user->rf_lpudoctor_id = 5015;
$patientService = \Mockery::mock(PatientService::class);
$unifiedPatientService = \Mockery::mock(UnifiedPatientService::class);
$unifiedPatientService
->shouldReceive('getLivePatientCountByStatus')
->once()->with($department, $user, 'plan', \Mockery::type(DateRange::class), 10, true)->andReturn(11);
$unifiedPatientService
->shouldReceive('getLivePatientCountByStatus')
->once()->with($department, $user, 'emergency', \Mockery::type(DateRange::class), 10, true)->andReturn(7);
$unifiedPatientService
->shouldReceive('getLivePatientCountByStatus')
->once()->with($department, $user, 'recipient', \Mockery::type(DateRange::class), 10)->andReturn(4);
$unifiedPatientService
->shouldReceive('getLivePatientCountByStatus')
->once()->with($department, $user, 'outcome-discharged', \Mockery::type(DateRange::class), 10)->andReturn(3);
$unifiedPatientService
->shouldReceive('getLivePatientCountByStatus')
->once()->with($department, $user, 'outcome-transferred', \Mockery::type(DateRange::class), 10)->andReturn(2);
$unifiedPatientService
->shouldReceive('getLivePatientCountByStatus')
->once()->with($department, $user, 'outcome-deceased', \Mockery::type(DateRange::class), 10)->andReturn(1);
$unifiedPatientService
->shouldReceive('getLivePatientCountByStatus')
->once()->with($department, $user, 'current', \Mockery::type(DateRange::class), 10)->andReturn(21);
$patientService
->shouldReceive('getSurgicalPatients')
->once()->with('plan', 10, \Mockery::type(DateRange::class), true)->andReturn(8);
$patientService
->shouldReceive('getSurgicalPatients')
->once()->with('emergency', 10, \Mockery::type(DateRange::class), true)->andReturn(5);
$service = new ReportService(
app(DateRangeService::class),
$unifiedPatientService,
$patientService,
\Mockery::mock(SnapshotService::class),
\Mockery::mock(\App\Services\StatisticsService::class)
);
$payload = $service->buildAutoFillReportPayload($user, $department, autoFillRange());
expect($payload['departmentId'])->toBe(10)
->and($payload['userId'])->toBe(5015)
->and($payload['metrics']['metrika_item_4'])->toBe(11)
->and($payload['metrics']['metrika_item_12'])->toBe(7)
->and($payload['metrics']['metrika_item_13'])->toBe(2)
->and($payload['metrics']['metrika_item_8'])->toBe(21)
->and($payload['metrics']['metrika_item_7'])->toBe(4);
});
it('creates auto-filled report through report service with auto flag and scoped department user', function () {
$department = new Department;
$department->department_id = 10;
$department->rf_mis_department_id = 100;
$user = new User;
$user->id = 15;
$user->rf_lpudoctor_id = 5015;
$user->rf_department_id = 999;
$payload = ['departmentId' => 10, 'metrics' => ['metrika_item_4' => 11], 'dates' => [1, 2]];
$reportService = \Mockery::mock(ReportService::class);
$reportService
->shouldReceive('buildAutoFillReportPayload')
->once()
->withArgs(function (User $scopedUser, Department $argDepartment, DateRange $dateRange) use ($department) {
return $argDepartment->department_id === $department->department_id
&& $dateRange->endSql() === '2026-04-09 06:00:00'
&& $scopedUser !== null
&& $scopedUser->rf_department_id === 10
&& $scopedUser->department->department_id === 10;
})
->andReturn($payload);
$reportService
->shouldReceive('storeReport')
->once()
->withArgs(function (array $data, User $scopedUser, bool $fillableAuto) use ($payload) {
return $data === $payload
&& $fillableAuto === true
&& $scopedUser->rf_department_id === 10
&& $scopedUser->department->department_id === 10;
})
->andReturn(new \App\Models\Report);
$service = new AutoReportService($reportService, app(DateRangeService::class));
expect($service->createReportForDate($user, $department, autoFillRange(), false))->toBeTrue();
});
it('force recreation removes previous report scoped data before storing a new auto-filled report', function () {
$department = new Department;
$department->department_id = 10;
$department->rf_mis_department_id = 100;
$user = new User;
$user->id = 15;
$user->rf_lpudoctor_id = 5015;
$user->rf_department_id = 10;
DB::table('reports')->insert([
'report_id' => 55,
'created_at' => '2026-04-09',
'sent_at' => '2026-04-09 06:00:00',
'rf_department_id' => 10,
'rf_user_id' => 15,
'rf_lpudoctor_id' => 5015,
]);
DB::table('metrika_results')->insert([
'rf_report_id' => 55,
'rf_metrika_item_id' => 4,
'value' => '99',
]);
DB::table('medical_history_snapshots')->insert([
'rf_report_id' => 55,
'rf_medicalhistory_id' => 100,
'patient_type' => 'plan',
'patient_uid' => 'mis:100',
'patient_source_type' => 'mis',
'patient_kind' => 'plan',
'full_name' => 'Old Snapshot',
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('unwanted_events')->insert([
'rf_report_id' => 55,
]);
DB::table('observation_patients')->insert([
'rf_report_id' => 55,
'rf_department_id' => 10,
'rf_medicalhistory_id' => 100,
]);
$payload = ['departmentId' => 10, 'metrics' => ['metrika_item_4' => 11], 'dates' => [1, 2]];
$reportService = \Mockery::mock(ReportService::class);
$reportService
->shouldReceive('buildAutoFillReportPayload')
->once()
->andReturn($payload);
$reportService
->shouldReceive('storeReport')
->once()
->andReturn(new \App\Models\Report);
$service = new AutoReportService($reportService, app(DateRangeService::class));
expect($service->createReportForDate($user, $department, autoFillRange(), true))->toBeTrue()
->and(DB::table('reports')->where('report_id', 55)->exists())->toBeFalse()
->and(DB::table('metrika_results')->where('rf_report_id', 55)->exists())->toBeFalse()
->and(DB::table('medical_history_snapshots')->where('rf_report_id', 55)->exists())->toBeFalse()
->and(DB::table('unwanted_events')->where('rf_report_id', 55)->exists())->toBeFalse()
->and(DB::table('observation_patients')->where('rf_report_id', 55)->exists())->toBeFalse();
});
it('reports fill command chooses doctor by default and honors explicit user option', function () {
DB::table('departments')->insert([
'department_id' => 10,
'name_short' => 'Отд. 10',
'rf_mis_department_id' => 100,
]);
DB::table('roles')->insert([
['role_id' => 1, 'name' => 'Doctor', 'slug' => 'doctor', 'is_active' => true],
['role_id' => 2, 'name' => 'Head', 'slug' => 'head_of_department', 'is_active' => true],
]);
DB::table('users')->insert([
[
'id' => 101,
'name' => 'Doctor A',
'login' => 'doc-a',
'password' => 'secret',
'is_active' => true,
'rf_lpudoctor_id' => 1001,
'rf_department_id' => 10,
'created_at' => now(),
'updated_at' => now(),
],
[
'id' => 102,
'name' => 'Head B',
'login' => 'head-b',
'password' => 'secret',
'is_active' => true,
'rf_lpudoctor_id' => 1002,
'rf_department_id' => 10,
'created_at' => now(),
'updated_at' => now(),
],
[
'id' => 103,
'name' => 'Doctor C',
'login' => 'doc-c',
'password' => 'secret',
'is_active' => true,
'rf_lpudoctor_id' => 1003,
'rf_department_id' => 10,
'created_at' => now(),
'updated_at' => now(),
],
]);
DB::table('user_roles')->insert([
['user_role_id' => 1, 'rf_user_id' => 101, 'rf_role_id' => 1, 'is_active' => true, 'is_default' => true],
['user_role_id' => 2, 'rf_user_id' => 102, 'rf_role_id' => 2, 'is_active' => true, 'is_default' => true],
['user_role_id' => 3, 'rf_user_id' => 103, 'rf_role_id' => 1, 'is_active' => true, 'is_default' => true],
]);
DB::table('user_departments')->insert([
['id' => 1, 'rf_user_id' => 101, 'rf_department_id' => 10, 'is_favorite' => true, 'order' => 2, 'user_name' => 'Doctor A'],
['id' => 2, 'rf_user_id' => 102, 'rf_department_id' => 10, 'is_favorite' => true, 'order' => 1, 'user_name' => 'Head B'],
['id' => 3, 'rf_user_id' => 103, 'rf_department_id' => 10, 'is_favorite' => false, 'order' => 1, 'user_name' => 'Doctor C'],
]);
$autoReportService = \Mockery::mock(AutoReportService::class);
$autoReportService
->shouldReceive('fillReportsForUser')
->once()
->withArgs(function (User $user, string $startDate, string $endDate, Department $department, bool $force) {
return $user->id === 101
&& $startDate === '2026-01-01'
&& $endDate === '2026-01-02'
&& $department->department_id === 10
&& $force === false;
})
->andReturn(2);
$autoReportService
->shouldReceive('fillReportsForUser')
->once()
->withArgs(function (User $user, string $startDate, string $endDate, Department $department, bool $force) {
return $user->id === 103
&& $startDate === '2026-01-01'
&& $endDate === '2026-01-02'
&& $department->department_id === 10
&& $force === false;
})
->andReturn(2);
app()->instance(AutoReportService::class, $autoReportService);
$command = app(FillReportsFromDate::class);
$command->setLaravel(app());
$tester = new CommandTester($command);
expect($tester->execute([
'--date' => '2026-01-01',
'--end-date' => '2026-01-02',
'--department' => 10,
]))->toBe(0);
expect($tester->execute([
'--date' => '2026-01-01',
'--end-date' => '2026-01-02',
'--department' => 10,
'--user' => 103,
]))->toBe(0);
});