id(); $table->string('name')->nullable(); $table->string('login')->nullable(); $table->string('password')->nullable(); $table->timestamps(); }); Schema::create('departments', function (Blueprint $table) { $table->id('department_id'); $table->string('name_full')->nullable(); $table->string('name_short')->nullable(); $table->integer('rf_mis_department_id')->nullable(); $table->integer('rf_department_type')->nullable(); }); Schema::create('department_metrika_defaults', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('rf_department_id'); $table->unsignedBigInteger('rf_metrika_item_id'); $table->string('value')->nullable(); }); Schema::create('reports', function (Blueprint $table) { $table->id('report_id'); $table->dateTime('created_at'); $table->dateTime('sent_at')->nullable(); $table->unsignedBigInteger('rf_department_id'); $table->unsignedBigInteger('rf_user_id')->nullable(); $table->unsignedBigInteger('rf_lpudoctor_id')->nullable(); $table->dateTime('period_start')->nullable(); $table->dateTime('period_end')->nullable(); $table->string('status')->default('draft'); }); 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('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(); $table->text('comment')->nullable(); }); Schema::create('unwanted_events', function (Blueprint $table) { $table->id('unwanted_event_id'); $table->unsignedBigInteger('rf_report_id')->nullable(); $table->text('comment')->nullable(); $table->string('title')->nullable(); $table->boolean('is_visible')->default(true); $table->timestamps(); }); DB::table('users')->insert([ 'id' => 15, 'name' => 'Doctor', 'login' => 'doc', 'password' => 'secret', 'created_at' => now(), 'updated_at' => now(), ]); DB::table('departments')->insert([ 'department_id' => 10, 'name_full' => 'Department', 'name_short' => 'Dept', 'rf_mis_department_id' => 100, ]); DB::table('department_metrika_defaults')->insert([ 'rf_department_id' => 10, 'rf_metrika_item_id' => 1, 'value' => '30', ]); }); afterEach(function () { \Mockery::close(); }); it('saves report snapshot idempotently through eloquent repository', function () { $adapter = \Mockery::mock(LegacyReportServiceAdapter::class); $adapter->shouldReceive('prepareMemoryForHeavySave')->twice(); $adapter->shouldReceive('createPatientSnapshots')->twice(); $adapter->shouldReceive('syncCalculatedMetrics')->twice(); $adapter->shouldReceive('saveLethalMetricFromSnapshots')->twice(); $adapter->shouldReceive('clearCacheAfterReportCreation')->twice(); $repository = new EloquentReportRepository($adapter); $snapshot = new ReportSnapshot( departmentId: 10, userId: 5015, actorUserId: 15, periodStart: new DateTimeImmutable('2026-04-08 06:00:00'), periodEnd: new DateTimeImmutable('2026-04-09 06:00:00'), status: 'draft', metrics: [4 => 11], observationPatients: [['medical_history_id' => 100, 'comment' => 'watch']], unwantedEvents: [['title' => 'event', 'comment' => 'test', 'is_visible' => true]], ); $first = $repository->save($snapshot); $second = $repository->save(new ReportSnapshot( departmentId: 10, userId: 5015, actorUserId: 15, periodStart: new DateTimeImmutable('2026-04-08 06:00:00'), periodEnd: new DateTimeImmutable('2026-04-09 06:00:00'), status: 'draft', metrics: [4 => 12], observationPatients: [['medical_history_id' => 100, 'comment' => 'watch-2']], unwantedEvents: [['title' => 'event-2', 'comment' => 'test-2', 'is_visible' => true]], reportId: $first->reportId, )); expect($first->reportId)->toBe($second->reportId) ->and(DB::table('reports')->count())->toBe(1) ->and(DB::table('metrika_results')->where('rf_report_id', $first->reportId)->where('rf_metrika_item_id', 4)->value('value'))->toBe('12') ->and(DB::table('observation_patients')->where('rf_report_id', $first->reportId)->value('comment'))->toBe('watch-2'); });