setupUser(); $role = Role::firstOrCreate(['name' => 'Owner']); $permissions = ['view order', 'create 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 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, 'quality' => 'Premium', '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 add a new perfume with custom quality', function () { $perfume = Perfume::factory()->create(['sale_price' => 1000]); $bottle = Bottle::factory()->create(['size' => 'Custom', 'sale_price' => 5000]); $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', 'custom') ->set('form.bottle_size', '15') ->call('addPerfume') ->assertHasNoErrors(); $this->assertDatabaseHas('order_items', [ 'user_id' => $this->user->id, 'orderable_type' => Perfume::class, 'orderable_id' => $perfume->id, 'quantity' => 15, 'quality' => 'Custom', 'order_id' => null, ]); }); it('can add a refill perfume with custom quality', function () { $perfume = Perfume::factory()->create(['sale_price' => 1000]); $this->outlet->perfumes()->attach($perfume->id, ['stock' => 1000]); Livewire::actingAs($this->user) ->test(Create::class) ->set('form.outlet_id', $this->outlet->id) ->set('tab', 'refill') ->set('form.perfume_id', $perfume->id) ->set('form.quality_id', 'custom') ->set('form.bottle_size', '50') ->call('addPerfume') ->assertHasNoErrors(); $this->assertDatabaseHas('order_items', [ 'user_id' => $this->user->id, 'orderable_type' => Perfume::class, 'orderable_id' => $perfume->id, 'quantity' => 50, 'quality' => 'Custom', 'order_id' => null, ]); }); it('separates items with different qualities', function () { $perfume = Perfume::factory()->create(['sale_price' => 1000]); $bottle = Bottle::factory()->create(['size' => '30ml']); $formula1 = Formula::factory()->create(['size' => '30ml', 'volume' => 30, 'quality' => 'Premium']); $formula2 = Formula::factory()->create(['size' => '30ml', 'volume' => 30, 'quality' => 'Super']); $this->outlet->perfumes()->attach($perfume->id, ['stock' => 1000]); $this->outlet->bottles()->attach($bottle->id, ['stock' => 100]); 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', $formula1->id) ->call('addPerfume') ->set('form.perfume_id', $perfume->id) ->set('form.bottle_id', $bottle->id) ->set('form.quality_id', $formula2->id) // Switch quality ->call('addPerfume'); $this->assertDatabaseHas('order_items', [ 'orderable_id' => $perfume->id, 'quality' => 'Premium', 'quantity' => 30, ]); $this->assertDatabaseHas('order_items', [ 'orderable_id' => $perfume->id, 'quality' => 'Super', 'quantity' => 30, ]); // Bottle should have aggregated to 2 (since bottle has no quality separation) $this->assertDatabaseHas('order_items', [ 'orderable_id' => $bottle->id, 'quantity' => 2, ]); }); 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('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 create without permission', function () { $this->user->roles()->detach(); $this->user->permissions()->detach(); $this->actingAs($this->user) ->get(route('studio.manage.order.create')) ->assertForbidden(); });