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); });