-
- Baru
- Isi Ulang
-
+ @if ($form->outlet_id)
+
+
+
+
+ Baru
+ Isi Ulang
+
-
-
-
- @foreach ($perfumes as $key => $perfume)
-
- {{ $perfume }}
-
- @endforeach
-
+
+
+
+ @foreach ($perfumes as $key => $perfume)
+
+ {{ $perfume }}
+
+ @endforeach
+
-
- @foreach ($bottles as $key => $bottle)
- {{ $bottle }}
-
- @endforeach
-
+
+ @foreach ($bottles as $key => $bottle)
+ {{ $bottle }}
+
+ @endforeach
+
-
- @if (!empty($qualities))
-
- @foreach ($qualities as $key => $value)
-
- {{ $value }}
-
- @endforeach
-
- @else
-
- Kualitas
-
- Silakan pilih botol...
-
- @endif
+
+ @if (!empty($qualities))
+
+ @foreach ($qualities as $key => $value)
+
+ {{ $value }}
+
+ @endforeach
+
+ @else
+
+ Kualitas
+
+ Silakan pilih
+ botol...
+
+ @endif
+
-
-
+
-
-
-
- @foreach ($perfumes as $key => $perfume)
-
- {{ $perfume }}
-
- @endforeach
-
+
+
+
+ @foreach ($perfumes as $key => $perfume)
+
+ {{ $perfume }}
+
+ @endforeach
+
-
- Ukuran Botol
-
-
- ml
-
-
+
+ Ukuran Botol
+
+
+ ml
+
+
-
- @if (!empty($qualities))
-
- @foreach ($qualities as $key => $value)
-
- {{ $value }}
-
- @endforeach
-
- @else
-
- Kualitas
-
- Silakan masukan ukuran
- botol...
-
- @endif
+
+ @if (!empty($qualities))
+
+ @foreach ($qualities as $key => $value)
+
+ {{ $value }}
+
+ @endforeach
+
+ @else
+
+ Kualitas
+
+ Silakan masukan ukuran
+ botol...
+
+ @endif
+
-
-
-
+
+
-
-
- Tambah
-
-
+
+
+ Tambah
+
+
+
+
+
+
+ @foreach ($products as $key => $product)
+ {{ $product }}
+
+ @endforeach
+
+
+
+
+
+
+ Tambah
+
+
+
+
+ @else
+
+ Silakan pilih Outlet terlebih dahulu untuk menampilkan daftar item.
-
-
-
- @foreach ($products as $key => $product)
- {{ $product }}
-
- @endforeach
-
-
-
-
-
-
- Tambah
-
-
-
-
+ @endif
diff --git a/tests/Feature/Studio/Manage/OrderTest.php b/tests/Feature/Studio/Manage/OrderTest.php
new file mode 100644
index 0000000..c9d3e48
--- /dev/null
+++ b/tests/Feature/Studio/Manage/OrderTest.php
@@ -0,0 +1,354 @@
+setupUser();
+
+ $role = Role::firstOrCreate(['name' => 'Owner']);
+ $permissions = ['view order', 'create order', 'delete order'];
+ foreach ($permissions as $p) {
+ Permission::firstOrCreate(['name' => $p]);
+ }
+ $role->syncPermissions($permissions);
+ $this->user->assignRole($role);
+
+ // Ensure user has an outlet
+ $this->user->outlets()->sync([$this->outlet->id]);
+
+ $this->tier = \App\Models\Tier::factory()->create();
+
+ // Create other roles required by the component logic
+ Role::firstOrCreate(['name' => 'Developer']);
+ Role::firstOrCreate(['name' => 'Leader']);
+});
+
+it('renders the order index page', function () {
+ $this->actingAs($this->user)
+ ->get(route('studio.manage.order.index'))
+ ->assertOk()
+ ->assertSeeLivewire(Index::class);
+});
+
+it('renders the order create page', function () {
+ $this->actingAs($this->user)
+ ->get(route('studio.manage.order.create'))
+ ->assertOk()
+ ->assertSeeLivewire(Create::class);
+});
+
+it('can add a product to the cart', function () {
+ $product = Product::factory()->create(['sale_price' => 50000]);
+ $this->outlet->products()->attach($product->id, ['stock' => 100]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.product_id', $product->id)
+ ->set('form.quantity_product', '2')
+ ->call('addProduct')
+ ->assertHasNoErrors();
+
+ $this->assertDatabaseHas('order_items', [
+ 'user_id' => $this->user->id,
+ 'orderable_type' => Product::class,
+ 'orderable_id' => $product->id,
+ 'quantity' => 2,
+ 'unit_price' => 50000,
+ 'order_id' => null,
+ ]);
+});
+
+it('can add a new perfume (perfume + bottle) to the cart', function () {
+ $perfume = Perfume::factory()->create(['sale_price' => 1000]); // per ml
+ $bottle = Bottle::factory()->create(['size' => '30ml', 'sale_price' => 15000]);
+ $formula = Formula::factory()->create(['size' => '30ml', 'volume' => 30, 'quality' => 'Premium']);
+
+ $this->outlet->perfumes()->attach($perfume->id, ['stock' => 1000]);
+ $this->outlet->bottles()->attach($bottle->id, ['stock' => 50]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('tab', 'new')
+ ->set('form.perfume_id', $perfume->id)
+ ->set('form.bottle_id', $bottle->id)
+ ->set('form.quality_id', $formula->id)
+ ->call('addPerfume')
+ ->assertHasNoErrors();
+
+ // Should add both perfume and bottle
+ $this->assertDatabaseHas('order_items', [
+ 'user_id' => $this->user->id,
+ 'orderable_type' => Perfume::class,
+ 'orderable_id' => $perfume->id,
+ 'quantity' => 30,
+ 'order_id' => null,
+ ]);
+
+ $this->assertDatabaseHas('order_items', [
+ 'user_id' => $this->user->id,
+ 'orderable_type' => Bottle::class,
+ 'orderable_id' => $bottle->id,
+ 'quantity' => 1,
+ 'order_id' => null,
+ ]);
+});
+
+it('can update item quantity in the cart', function () {
+ $product = Product::factory()->create(['sale_price' => 50000]);
+ $item = OrderItem::factory()->forProduct($product)->create([
+ 'user_id' => $this->user->id,
+ 'order_id' => null,
+ 'quantity' => 1,
+ 'unit_price' => 50000,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.item_id', $item->id)
+ ->set('form.quantity_edit', '5')
+ ->call('updateItem')
+ ->assertHasNoErrors();
+
+ $this->assertDatabaseHas('order_items', [
+ 'id' => $item->id,
+ 'quantity' => 5,
+ ]);
+});
+
+it('can delete an item from the cart', function () {
+ $product = Product::factory()->create();
+ $item = OrderItem::factory()->forProduct($product)->create([
+ 'user_id' => $this->user->id,
+ 'order_id' => null,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->call('deleteItem', $item->id)
+ ->assertHasNoErrors();
+
+ $this->assertSoftDeleted('order_items', ['id' => $item->id]);
+});
+
+it('can store an order and update outlet stock', function () {
+ $product = Product::factory()->create(['sale_price' => 100000, 'cost_price' => 60000]);
+ $this->outlet->products()->attach($product->id, ['stock' => 10]);
+
+ OrderItem::factory()->forProduct($product)->create([
+ 'user_id' => $this->user->id,
+ 'order_id' => null,
+ 'quantity' => 2,
+ 'unit_price' => 100000,
+ 'cogs' => 60000,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.payment_method', '1') // Cash
+ ->call('save')
+ ->assertHasNoErrors()
+ ->assertRedirect();
+
+ $this->assertDatabaseHas('orders', [
+ 'outlet_id' => $this->outlet->id,
+ 'user_id' => $this->user->id,
+ 'total' => 200000,
+ 'subtotal' => 200000,
+ ]);
+
+ // Check payment created
+ $order = Order::latest()->first();
+ $this->assertDatabaseHas('payments', [
+ 'order_id' => $order->id,
+ 'amount' => 200000,
+ 'status' => PaymentStatus::PAID->value,
+ ]);
+
+ // Check stock updated
+ $this->assertDatabaseHas('outlet_product', [
+ 'outlet_id' => $this->outlet->id,
+ 'product_id' => $product->id,
+ 'stock' => 8,
+ ]);
+});
+
+it('can store an order with member and earn points', function () {
+ $product = Product::factory()->create(['sale_price' => 100000]);
+ $this->outlet->products()->attach($product->id, ['stock' => 10]);
+
+ $memberUser = User::factory()->create();
+ $memberUser->customer()->create([
+ 'name' => 'Member Test',
+ 'gender' => Gender::MALE->value,
+ ]);
+ $memberUser->membership()->create([
+ 'total_spending' => 0,
+ 'reward_points' => 0,
+ 'tier_id' => $this->tier->id,
+ ]);
+
+ OrderItem::factory()->forProduct($product)->create([
+ 'user_id' => $this->user->id,
+ 'order_id' => null,
+ 'quantity' => 1,
+ 'unit_price' => 100000,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.member_id', $memberUser->customer->id)
+ ->call('save')
+ ->assertHasNoErrors();
+
+ // Check points earned (100000 / 5000 = 20 points)
+ $this->assertDatabaseHas('memberships', [
+ 'user_id' => $memberUser->id,
+ 'reward_points' => 20,
+ 'total_spending' => 100000,
+ ]);
+
+ $this->assertDatabaseHas('point_records', [
+ 'user_id' => $memberUser->id,
+ 'change' => 20,
+ 'is_addition' => true,
+ ]);
+});
+
+it('can store an order with voucher', function () {
+ $product = Product::factory()->create(['sale_price' => 100000]);
+ $this->outlet->products()->attach($product->id, ['stock' => 10]);
+
+ $memberUser = User::factory()->create();
+ $memberUser->customer()->create([
+ 'name' => 'Voucher User',
+ 'gender' => Gender::MALE->value,
+ ]);
+ $memberUser->membership()->create(['tier_id' => $this->tier->id]);
+
+ $voucher = Voucher::factory()->create([
+ 'type' => VoucherType::FIXED->value,
+ 'discount_amount' => 20000,
+ ]);
+
+ // Assign voucher to user
+ DB::table('user_voucher')->insert([
+ 'user_id' => $memberUser->id,
+ 'voucher_id' => $voucher->id,
+ 'quantity' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+
+ OrderItem::factory()->forProduct($product)->create([
+ 'user_id' => $this->user->id,
+ 'order_id' => null,
+ 'quantity' => 1,
+ 'unit_price' => 100000,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.member_id', $memberUser->customer->id)
+ ->set('form.voucher_id', $voucher->id)
+ ->call('save')
+ ->assertHasNoErrors();
+
+ $this->assertDatabaseHas('orders', [
+ 'voucher_id' => $voucher->id,
+ 'total' => 80000,
+ 'discount' => 20000,
+ ]);
+
+ // Check voucher used up
+ $this->assertDatabaseMissing('user_voucher', [
+ 'user_id' => $memberUser->id,
+ 'voucher_id' => $voucher->id,
+ ]);
+});
+
+it('can delete an order and restore outlet stock', function () {
+ $product = Product::factory()->create();
+ $order = Order::factory()->create([
+ 'outlet_id' => $this->outlet->id,
+ 'user_id' => $this->user->id,
+ 'total' => 100000,
+ ]);
+
+ $item = OrderItem::factory()->forProduct($product)->create([
+ 'order_id' => $order->id,
+ 'user_id' => $this->user->id,
+ 'quantity' => 2,
+ ]);
+
+ // Initial stock (after order was supposedly created)
+ $this->outlet->products()->attach($product->id, ['stock' => 10]);
+
+ Livewire::actingAs($this->user)
+ ->test(Index::class)
+ ->call('delete', $order->id)
+ ->assertHasNoErrors();
+
+ $this->assertSoftDeleted('orders', ['id' => $order->id]);
+
+ // Check stock restored (10 + 2 = 12)
+ $this->assertDatabaseHas('outlet_product', [
+ 'outlet_id' => $this->outlet->id,
+ 'product_id' => $product->id,
+ 'stock' => 12,
+ ]);
+});
+
+it('cannot create order with empty cart', function () {
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->call('save')
+ ->assertHasNoErrors(); // Shows toast
+
+ $this->assertDatabaseCount('orders', 0);
+});
+
+it('validates required fields', function () {
+ // Add something to cart first to pass empty cart check
+ OrderItem::factory()->forProduct()->create(['user_id' => $this->user->id, 'order_id' => null]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.outlet_id', '')
+ ->call('save')
+ ->assertHasErrors(['form.outlet_id' => 'required']);
+});
+
+it('cannot access order without permission', function () {
+ $this->user->roles()->detach();
+ $this->user->permissions()->detach();
+
+ $this->actingAs($this->user)
+ ->get(route('studio.manage.order.index'))
+ ->assertForbidden();
+
+ $this->actingAs($this->user)
+ ->get(route('studio.manage.order.create'))
+ ->assertForbidden();
+});
diff --git a/tests/Feature/Studio/Manage/RestockTest.php b/tests/Feature/Studio/Manage/RestockTest.php
new file mode 100644
index 0000000..283d355
--- /dev/null
+++ b/tests/Feature/Studio/Manage/RestockTest.php
@@ -0,0 +1,190 @@
+setupUser();
+ $this->outlet = $this->user->outlets->first();
+
+ // Create permissions
+ $permissions = [
+ 'view restock',
+ 'create restock',
+ 'update restock',
+ 'delete restock',
+ ];
+
+ foreach ($permissions as $permission) {
+ Permission::firstOrCreate(['name' => $permission]);
+ }
+
+ $role = Role::create(['name' => 'Owner']);
+ $role->givePermissionTo($permissions);
+ $this->user->assignRole($role);
+});
+
+it('renders the restock index page', function () {
+ Livewire::actingAs($this->user)
+ ->test(Index::class)
+ ->assertStatus(200);
+});
+
+it('renders the restock create page', function () {
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->assertStatus(200);
+});
+
+it('can add items to restock cart', function () {
+ $warehouse = Warehouse::factory()->create();
+ $perfume = Perfume::factory()->create(['name' => 'Test Perfume']);
+
+ // Add stock to warehouse
+ $warehouse->perfumes()->attach($perfume->id, ['stock' => 100]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.warehouse_id', $warehouse->id)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.perfume_id', $perfume->id)
+ ->set('form.quantity_perfume', 10)
+ ->call('addItem', 'parfum')
+ ->assertSee('Test Perfume')
+ ->assertSee(10);
+});
+
+it('can store a restock and transfer stock', function () {
+ $warehouse = Warehouse::factory()->create();
+ $perfume = Perfume::factory()->create();
+ $product = Product::factory()->create();
+ $bottle = Bottle::factory()->create();
+
+ // Seed initial stock
+ $warehouse->perfumes()->attach($perfume->id, ['stock' => 100]);
+ $warehouse->products()->attach($product->id, ['stock' => 100]);
+ $warehouse->bottles()->attach($bottle->id, ['stock' => 100]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.warehouse_id', $warehouse->id)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.restock_date', now()->format('Y-m-d'))
+ ->set('form.note', 'Test Restock')
+
+ // Add items
+ ->set('form.perfume_id', $perfume->id)
+ ->set('form.quantity_perfume', 10)
+ ->call('addItem', 'parfum')
+
+ ->set('form.product_id', $product->id)
+ ->set('form.quantity_product', 5)
+ ->call('addItem', 'produk')
+
+ ->set('form.bottle_id', $bottle->id)
+ ->set('form.quantity_bottle', 20)
+ ->call('addItem', 'botol')
+
+ ->call('save')
+ ->assertRedirect(route('studio.manage.restock.index', ['notification' => 'Restock berhasil diproses.']));
+
+ // Verify DB records
+ $this->assertDatabaseHas('restocks', [
+ 'warehouse_id' => $warehouse->id,
+ 'outlet_id' => $this->outlet->id,
+ 'note' => 'Test Restock',
+ ]);
+
+ // Verify Warehouse Stock Decreased
+ $this->assertEquals(90, $warehouse->perfumes()->find($perfume->id)->pivot->stock);
+ $this->assertEquals(95, $warehouse->products()->find($product->id)->pivot->stock);
+ $this->assertEquals(80, $warehouse->bottles()->find($bottle->id)->pivot->stock);
+
+ // Verify Outlet Stock Increased
+ $this->assertEquals(10, $this->outlet->perfumes()->find($perfume->id)->pivot->stock);
+ $this->assertEquals(5, $this->outlet->products()->find($product->id)->pivot->stock);
+ $this->assertEquals(20, $this->outlet->bottles()->find($bottle->id)->pivot->stock);
+});
+
+it('cannot restock more than available warehouse stock', function () {
+ $warehouse = Warehouse::factory()->create();
+ $perfume = Perfume::factory()->create();
+ $warehouse->perfumes()->attach($perfume->id, ['stock' => 10]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.warehouse_id', $warehouse->id)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.restock_date', now()->format('Y-m-d'))
+ ->set('form.perfume_id', $perfume->id)
+ ->set('form.quantity_perfume', 20) // Requesting more than 10
+ ->call('addItem', 'parfum');
+
+ // Assert that the item was NOT added to the cart
+ $this->assertDatabaseMissing('restock_items', [
+ 'restockable_id' => $perfume->id,
+ 'quantity' => 20,
+ ]);
+});
+
+it('can delete a restock and reverse stock transfer', function () {
+ $warehouse = Warehouse::factory()->create();
+ $perfume = Perfume::factory()->create();
+
+ // Initial state: Warehouse 90, Outlet 10 (simulating after a restock of 10)
+ $warehouse->perfumes()->attach($perfume->id, ['stock' => 90]);
+ $this->outlet->perfumes()->attach($perfume->id, ['stock' => 10]);
+
+ $restock = Restock::create([
+ 'warehouse_id' => $warehouse->id,
+ 'outlet_id' => $this->outlet->id,
+ 'restock_date' => now(),
+ 'note' => 'To be deleted',
+ ]);
+
+ $restock->items()->create([
+ 'user_id' => $this->user->id,
+ 'restockable_type' => Perfume::class,
+ 'restockable_id' => $perfume->id,
+ 'quantity' => 10,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Index::class)
+ ->call('delete', $restock->id);
+
+ $this->assertSoftDeleted('restocks', ['id' => $restock->id]);
+
+ // Verify Stock Reversal
+ $this->assertEquals(100, $warehouse->perfumes()->find($perfume->id)->pivot->stock); // 90 + 10
+ $this->assertEquals(0, $this->outlet->perfumes()->find($perfume->id)->pivot->stock); // 10 - 10
+});
+
+it('validates restock input', function () {
+ $warehouse = Warehouse::factory()->create();
+ $perfume = Perfume::factory()->create();
+ $warehouse->perfumes()->attach($perfume->id, ['stock' => 100]);
+
+ Livewire::actingAs($this->user)
+ ->test(Create::class)
+ ->set('form.warehouse_id', $warehouse->id)
+ ->set('form.outlet_id', $this->outlet->id)
+ ->set('form.perfume_id', $perfume->id)
+ ->set('form.quantity_perfume', 10)
+ ->call('addItem', 'parfum')
+
+ // Reset required fields
+ ->set('form.restock_date', '')
+ ->set('form.outlet_id', '') // Warehouse ID is needed for item adding, but outlet ID is validated on save
+
+ ->call('save')
+ ->assertHasErrors(['form.restock_date', 'form.outlet_id']);
+});
diff --git a/tests/Feature/Studio/Manage/StockOpnameTest.php b/tests/Feature/Studio/Manage/StockOpnameTest.php
new file mode 100644
index 0000000..f85555c
--- /dev/null
+++ b/tests/Feature/Studio/Manage/StockOpnameTest.php
@@ -0,0 +1,134 @@
+setupUser();
+ $this->outlet = $this->user->outlets->first();
+
+ // Create permissions
+ $permissions = [
+ 'view stock opname',
+ 'create stock opname',
+ 'manage stock opname',
+ 'show stock opname',
+ 'delete stock opname',
+ ];
+
+ foreach ($permissions as $permission) {
+ Permission::firstOrCreate(['name' => $permission]);
+ }
+
+ $role = Role::create(['name' => 'Owner']);
+ $role->givePermissionTo($permissions);
+ $this->user->assignRole($role);
+});
+
+it('renders the stock opname index page', function () {
+ Livewire::actingAs($this->user)
+ ->test(Index::class)
+ ->assertStatus(200);
+});
+
+it('can update physical quantity in manage page', function () {
+ // Create pre-existing StockOpname
+ $stockOpname = StockOpname::create([
+ 'outlet_id' => $this->outlet->id,
+ 'period_month' => now()->format('Y-m'),
+ 'status' => StockOpnameStatus::PROCESS,
+ ]);
+
+ $perfume = Perfume::factory()->create();
+ $item = StockOpnameItem::create([
+ 'stock_opname_id' => $stockOpname->id,
+ 'user_id' => $this->user->id,
+ 'itemable_type' => Perfume::class,
+ 'itemable_id' => $perfume->id,
+ 'qty_system' => 10,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Manage::class, ['stockOpname' => $stockOpname])
+ ->set('form.outlet_stock.'.$item->id, 8) // Physical count is 8
+ ->assertSet('form.outlet_stock.'.$item->id, 8);
+
+ // Manage component updates via updated hook, or explicitly called?
+ // Manage.php: updated(...) calls form->update().
+
+ $this->assertDatabaseHas('stock_opname_items', [
+ 'id' => $item->id,
+ 'qty_physical' => 8,
+ ]);
+});
+
+it('can request approval for stock opname', function () {
+ $stockOpname = StockOpname::create([
+ 'outlet_id' => $this->outlet->id,
+ 'period_month' => now()->format('Y-m'),
+ 'status' => StockOpnameStatus::PROCESS,
+ 'note' => 'Ready for approval',
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Index::class)
+ ->call('requestApproval', $stockOpname->id); // Passing ID or Model? Method signature says Model. Livewire handles ID binding.
+
+ $this->assertDatabaseHas('stock_opname', [
+ 'id' => $stockOpname->id,
+ 'status' => StockOpnameStatus::PENDING_APPROVAL->value,
+ ]);
+});
+
+it('can approve stock opname and update outlet stock', function () {
+ $stockOpname = StockOpname::create([
+ 'outlet_id' => $this->outlet->id,
+ 'period_month' => now()->format('Y-m'),
+ 'status' => StockOpnameStatus::PENDING_APPROVAL,
+ ]);
+
+ $perfume = Perfume::factory()->create();
+ // Verify initial stock of perfume in outlet (empty or 0)
+ // Attach to outlet first to mimic existing stock
+ $this->outlet->perfumes()->attach($perfume->id, ['stock' => 10]);
+
+ $item = StockOpnameItem::create([
+ 'stock_opname_id' => $stockOpname->id,
+ 'user_id' => $this->user->id,
+ 'itemable_type' => Perfume::class,
+ 'itemable_id' => $perfume->id,
+ 'qty_system' => 10,
+ 'qty_physical' => 8, // Found 8, so stock should become 8 (loss of 2)
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Index::class)
+ ->call('approveApproval', $stockOpname->id);
+
+ $this->assertDatabaseHas('stock_opname', [
+ 'id' => $stockOpname->id,
+ 'status' => StockOpnameStatus::COMPLETED->value,
+ ]);
+
+ // Verify outlet stock updated
+ $this->assertEquals(8, $this->outlet->perfumes()->find($perfume->id)->pivot->stock);
+});
+
+it('cannot manage stock opname if not in process status', function () {
+ $stockOpname = StockOpname::create([
+ 'outlet_id' => $this->outlet->id,
+ 'period_month' => now()->format('Y-m'),
+ 'status' => StockOpnameStatus::COMPLETED,
+ ]);
+
+ Livewire::actingAs($this->user)
+ ->test(Manage::class, ['stockOpname' => $stockOpname])
+ ->assertForbidden();
+});