496 lines
18 KiB
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);
|
|
}
|
|
}
|