TIF_Nganjuk_E41220879/tests/Feature/ResepTest.php

496 lines
18 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Kategori;
use App\Models\ObatKeluar;
use App\Models\ObatMasuk;
use App\Models\Resep;
use App\Models\ResepItem;
use App\Models\Satuan;
use App\Models\User;
use Tests\DatabaseTestCase;
class ResepTest extends DatabaseTestCase
{
private User $dokter;
private User $apoteker;
private ObatMasuk $obatMasuk;
protected function setUp(): void
{
parent::setUp();
$this->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);
}
}