dokter = User::factory()->create(['role' => 'dokter']); $this->apoteker = User::factory()->create(['role' => 'apoteker']); $kategori = Kategori::create(['nama' => 'Antibiotik']); $satuan = Satuan::create(['nama' => 'Tablet']); $this->obatMasuk = ObatMasuk::create([ 'nama_obat' => 'Amoxicillin', 'kategori_id' => $kategori->id, 'satuan_id' => $satuan->id, 'kode_batch' => 'BTH-001', 'stok' => 100, 'tanggal_penerimaan' => now()->toDateString(), 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), 'user_id' => $this->dokter->id, ]); } private function resepData(array $overrides = []): array { return array_merge([ 'nama_dokter' => 'Dr. Siti', 'tanggal_resep' => now()->toDateString(), 'jenis_penjamin' => 'umum', 'jenis_layanan' => 'BP', 'nama_pasien' => 'Budi Santoso', 'jenis_kelamin' => 'L', 'umur_pasien' => 30, 'berat_badan' => 65.5, 'diagnosa' => 'ISPA', 'items' => [ [ 'obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 10, 'aturan_pakai' => '3x sehari', ], ], ], $overrides); } private function createResep(array $attrs = []): Resep { return Resep::create(array_merge([ 'no_resep' => 'RSP-TEST-' . rand(1000, 9999), 'user_id' => $this->dokter->id, 'nama_dokter' => 'Dr. Siti', 'nama_pasien' => 'Budi Santoso', 'tanggal_resep' => now()->toDateString(), 'diagnosa' => 'Flu', 'status' => 'proses', 'jenis_penjamin' => 'umum', 'jenis_layanan' => 'BP', 'jenis_kelamin' => 'L', 'umur_pasien' => 30, 'berat_badan' => 60, 'is_read' => false, ], $attrs)); } // ───────────────────────────────────────── // index // ───────────────────────────────────────── public function test_index_returns_200_for_authenticated_user(): void { $this->actingAs($this->dokter); $response = $this->get(route('resep.index')); $response->assertStatus(200); } public function test_index_redirects_guest(): void { $response = $this->get(route('resep.index')); $response->assertRedirect(route('login')); } public function test_index_search_by_nama_pasien(): void { $this->actingAs($this->dokter); $this->createResep(['nama_pasien' => 'Budi Santoso', 'no_resep' => 'RSP-001']); $this->createResep(['nama_pasien' => 'Siti Rahayu', 'no_resep' => 'RSP-002']); $response = $this->get(route('resep.index', ['search' => 'Budi'])); $response->assertSee('Budi Santoso'); $response->assertDontSee('Siti Rahayu'); } public function test_index_filter_by_status(): void { $this->actingAs($this->dokter); $this->createResep(['no_resep' => 'RSP-001', 'status' => 'proses']); $this->createResep(['no_resep' => 'RSP-002', 'status' => 'selesai', 'nama_pasien' => 'Pasien Selesai']); $response = $this->get(route('resep.index', ['status' => 'proses'])); $response->assertSee('Budi Santoso'); $response->assertDontSee('Pasien Selesai'); } // ───────────────────────────────────────── // store — berhasil // ───────────────────────────────────────── public function test_store_creates_resep_with_items_and_obat_keluar(): void { $this->actingAs($this->dokter); $response = $this->post(route('resep.store'), $this->resepData()); $response->assertRedirect(route('resep.index')); $response->assertSessionHas('success'); // Resep terbuat $this->assertDatabaseHas('reseps', ['nama_pasien' => 'Budi Santoso', 'status' => 'proses']); // ResepItem terbuat $this->assertDatabaseHas('resep_items', [ 'obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 10, ]); // ObatKeluar terbuat $this->assertDatabaseHas('obat_keluars', [ 'obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 10, 'status' => 'proses', ]); } public function test_store_does_not_decrement_stok_when_status_is_proses(): void { $this->actingAs($this->dokter); $this->post(route('resep.store'), $this->resepData([ 'items' => [ ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 15, 'aturan_pakai' => '3x sehari'], ], ])); // Stok dari 100 tetap 100 karena statusnya 'proses' $this->assertEquals(100, $this->obatMasuk->fresh()->stok); } public function test_store_generates_unique_no_resep(): void { $this->actingAs($this->dokter); $this->post(route('resep.store'), $this->resepData()); $resep = Resep::first(); $this->assertStringStartsWith('RSP-', $resep->no_resep); } // ───────────────────────────────────────── // store — gagal karena stok // ───────────────────────────────────────── public function test_store_fails_when_item_stok_insufficient(): void { $this->actingAs($this->dokter); $response = $this->post(route('resep.store'), $this->resepData([ 'items' => [ ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 200, 'aturan_pakai' => '1x sehari'], ], ])); $response->assertSessionHasErrors('items'); $this->assertDatabaseCount('reseps', 0); } public function test_store_fails_without_required_fields(): void { $this->actingAs($this->dokter); $response = $this->post(route('resep.store'), []); $response->assertSessionHasErrors(['nama_dokter', 'tanggal_resep', 'nama_pasien', 'diagnosa', 'items']); } // ───────────────────────────────────────── // show // ───────────────────────────────────────── public function test_show_returns_200(): void { $this->actingAs($this->apoteker); $resep = $this->createResep(); $response = $this->get(route('resep.show', $resep)); $response->assertStatus(200); } public function test_show_marks_as_read_for_apoteker(): void { $this->actingAs($this->apoteker); $resep = $this->createResep(['is_read' => false]); $this->get(route('resep.show', $resep)); $this->assertTrue($resep->fresh()->is_read); } public function test_show_does_not_mark_as_read_for_dokter(): void { $this->actingAs($this->dokter); $resep = $this->createResep(['is_read' => false]); $this->get(route('resep.show', $resep)); $this->assertFalse($resep->fresh()->is_read); } // ───────────────────────────────────────── // edit — restrict selesai // ───────────────────────────────────────── public function test_edit_redirects_to_show_when_status_is_selesai(): void { $this->actingAs($this->dokter); $resep = $this->createResep(['status' => 'selesai']); $response = $this->get(route('resep.edit', $resep)); $response->assertRedirect(route('resep.show', $resep)); $response->assertSessionHas('error'); } public function test_edit_returns_200_when_status_is_proses(): void { $this->actingAs($this->dokter); $resep = $this->createResep(['status' => 'proses']); $response = $this->get(route('resep.edit', $resep)); $response->assertStatus(200); } // ───────────────────────────────────────── // update — restrict selesai // ───────────────────────────────────────── public function test_update_redirects_to_show_when_resep_is_selesai(): void { $this->actingAs($this->dokter); $resep = $this->createResep(['status' => 'selesai']); $response = $this->put(route('resep.update', $resep), $this->resepData(['status' => 'proses'])); $response->assertRedirect(route('resep.show', $resep)); $response->assertSessionHas('error'); } // ───────────────────────────────────────── // update — berhasil // ───────────────────────────────────────── public function test_update_does_not_deduct_stok_when_status_is_proses(): void { $this->actingAs($this->dokter); // Buat resep awal (status proses, stok utuh 100) $resep = $this->createResep(); ResepItem::create([ 'resep_id' => $resep->id, 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'jumlah' => 10, ]); // Update dengan item baru jumlah 20, status tetap proses $response = $this->put(route('resep.update', $resep), $this->resepData([ 'status' => 'proses', 'items' => [ ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 20, 'aturan_pakai' => '1x sehari'], ], ])); $response->assertRedirect(route('resep.index')); // Stok tetap 100 karena masih proses $this->assertEquals(100, $this->obatMasuk->fresh()->stok); } public function test_update_deducts_stok_when_status_changed_to_selesai(): void { $this->actingAs($this->dokter); // Buat resep awal (status proses, stok utuh 100) $resep = $this->createResep(); ResepItem::create([ 'resep_id' => $resep->id, 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'jumlah' => 10, ]); // Update status menjadi selesai dan jumlah menjadi 20 $response = $this->put(route('resep.update', $resep), $this->resepData([ 'status' => 'selesai', 'items' => [ ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 20, 'aturan_pakai' => '1x sehari'], ], ])); $response->assertRedirect(route('resep.index')); // Stok: berkurang 20 → 80 $this->assertEquals(80, $this->obatMasuk->fresh()->stok); } // ───────────────────────────────────────── // updateStatus // ───────────────────────────────────────── public function test_update_status_changes_resep_and_obat_keluar_status(): void { $this->actingAs($this->apoteker); $resep = $this->createResep(['status' => 'proses']); // Buat item resep ResepItem::create([ 'resep_id' => $resep->id, 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'jumlah' => 10, ]); // Buat obat keluar terkait dengan no_pengeluaran = no_resep ObatKeluar::create([ 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'no_pengeluaran' => $resep->no_resep, 'jumlah' => 10, 'tujuan_pemakaian' => 'Resep', 'tanggal_pengeluaran' => now()->toDateString(), 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), 'nama_petugas' => 'Petugas', 'nama_penerima' => 'Pasien', 'kode_batch' => 'BTH-001', 'status' => 'proses', 'user_id' => $this->apoteker->id, ]); $response = $this->patch(route('resep.update-status', $resep), [ 'status' => 'selesai', ]); $response->assertRedirect(route('resep.index')); $this->assertEquals('selesai', $resep->fresh()->status); // ObatKeluar terkait juga diupdate $this->assertDatabaseHas('obat_keluars', [ 'no_pengeluaran' => $resep->no_resep, 'status' => 'selesai', ]); // Stok harus berkurang karena status berubah menjadi selesai $this->assertEquals(90, $this->obatMasuk->fresh()->stok); } public function test_update_status_fails_when_resep_already_selesai(): void { $this->actingAs($this->apoteker); $resep = $this->createResep(['status' => 'selesai']); $response = $this->patch(route('resep.update-status', $resep), [ 'status' => 'dibatalkan', ]); $response->assertRedirect(route('resep.index')); $response->assertSessionHas('error'); // Status tidak berubah $this->assertEquals('selesai', $resep->fresh()->status); } public function test_update_status_validates_status_value(): void { $this->actingAs($this->apoteker); $resep = $this->createResep(['status' => 'proses']); $response = $this->patch(route('resep.update-status', $resep), [ 'status' => 'invalid-status', ]); $response->assertSessionHasErrors('status'); } // ───────────────────────────────────────── // destroy // ───────────────────────────────────────── public function test_destroy_deletes_resep_items_and_restores_stok_if_selesai(): void { $this->actingAs($this->dokter); // Buat resep dengan status selesai $resep = $this->createResep(['status' => 'selesai']); // Buat item resep dan kurangi stok (karena selesai) ResepItem::create([ 'resep_id' => $resep->id, 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'jumlah' => 20, ]); $this->obatMasuk->decrement('stok', 20); // Buat obat keluar terkait ObatKeluar::create([ 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'no_pengeluaran' => $resep->no_resep, 'jumlah' => 20, 'tujuan_pemakaian' => 'Resep test', 'tanggal_pengeluaran' => now()->toDateString(), 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), 'nama_petugas' => 'Petugas', 'nama_penerima' => 'Pasien', 'kode_batch' => 'BTH-001', 'status' => 'selesai', 'user_id' => $this->dokter->id, ]); $response = $this->delete(route('resep.destroy', $resep)); $response->assertRedirect(route('resep.index')); $response->assertSessionHas('success'); // Resep item dan resep terhapus $this->assertDatabaseMissing('reseps', ['id' => $resep->id]); $this->assertDatabaseMissing('resep_items', ['resep_id' => $resep->id]); // ObatKeluar terkait terhapus $this->assertDatabaseMissing('obat_keluars', ['no_pengeluaran' => $resep->no_resep]); // Stok dikembalikan: 80 → 100 (karena statusnya selesai) $this->assertEquals(100, $this->obatMasuk->fresh()->stok); } public function test_destroy_does_not_restore_stok_if_proses(): void { $this->actingAs($this->dokter); // Buat resep dengan status proses $resep = $this->createResep(['status' => 'proses']); // Buat item resep (stok tidak dikurangi karena proses) ResepItem::create([ 'resep_id' => $resep->id, 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'jumlah' => 20, ]); // Buat obat keluar terkait ObatKeluar::create([ 'obat_masuk_id' => $this->obatMasuk->id, 'nama_obat' => 'Amoxicillin', 'no_pengeluaran' => $resep->no_resep, 'jumlah' => 20, 'tujuan_pemakaian' => 'Resep test', 'tanggal_pengeluaran' => now()->toDateString(), 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), 'nama_petugas' => 'Petugas', 'nama_penerima' => 'Pasien', 'kode_batch' => 'BTH-001', 'status' => 'proses', 'user_id' => $this->dokter->id, ]); $response = $this->delete(route('resep.destroy', $resep)); $response->assertRedirect(route('resep.index')); $response->assertSessionHas('success'); // Stok tetap 100 karena belum pernah dikurangi (status proses) $this->assertEquals(100, $this->obatMasuk->fresh()->stok); } }