assertRedirect(route('login')); }); it('returns 403 when user has no permission to view orders', function () { actingAs(createUnauthorizedUser()) ->get(route('order.index')) ->assertStatus(403); }); }); describe('Order Module - Authorized Actions', function () { beforeEach(function () { $user = createAuthorizedUser([ 'View:Order', 'Create:Order', 'Edit:Order', 'Delete:Order', 'DeleteAny:Order', ]); actingAs($user); $settings = app(GeneralSettings::class); $settings->site_name = 'VN Grup'; $settings->site_phone = '08123456789'; $settings->site_address = 'Jl. Test No. 1'; $settings->site_description = 'Test Description'; $settings->save(); }); it('can access order index page', function () { Order::factory()->create(); get(route('order.index')) ->assertOk() ->assertInertia(fn ($page) => $page ->component('admin/manage/order/index') ->has('orders', fn ($page) => $page ->has('data.0.user') ->has('data.0.cogs_formatted') ->has('data.0.payment_formatted') ->has('data.0.created_at_formatted') ->etc() ) ); }); it('can access order create page', function () { get(route('order.create')) ->assertOk() ->assertInertia(fn ($page) => $page ->component('admin/manage/order/create') ->has('products') ->has('cartItems') ->where('orderStatus', function ($status) { return collect($status)->every(fn ($item) => $item['value'] !== OrderStatus::CANCELLED->value); }) ); }); it('can access order show page', function () { $order = Order::factory() ->has(OrderItem::factory()->count(2), 'items') ->create(); get(route('order.show', $order)) ->assertOk() ->assertInertia(fn ($page) => $page ->component('admin/manage/order/show') ->has('order', fn ($page) => $page ->where('id', $order->id) ->has('items', 2) ->has('items.0.product') ->has('user') ->etc() ) ->has('setting') ); }); it('can access order edit page', function () { $order = Order::factory()->create(); get(route('order.edit', $order)) ->assertOk() ->assertInertia(fn ($page) => $page ->component('admin/manage/order/edit') ->has('order') ->has('products') ->has('orderStatus') ->has('orderChannels') ->has('paymentMethods') ->has('priceTypes') ); }); it('can manage cart items', function () { $product = Product::factory()->create(['stock' => 10]); // 1. Add to cart postJson(route('order.addToCart'), [ 'product_id' => $product->id, 'qty' => 2, 'price' => 10000, 'price_type' => PriceType::RETAIL->value, ])->assertRedirect(); assertDatabaseHas('order_items', [ 'user_id' => auth()->id(), 'order_id' => null, 'product_id' => $product->id, 'qty' => 2, ]); $cartItem = OrderItem::where('product_id', $product->id)->whereNull('order_id')->first(); // 2. Update cart item patchJson(route('order.updateCartItem', $cartItem), [ 'product_id' => $product->id, 'qty' => 5, 'price' => 10000, 'price_type' => PriceType::RETAIL->value, ])->assertRedirect(); expect($cartItem->fresh()->qty)->toBe(5); // 3. Remove from cart deleteJson(route('order.removeFromCart', $cartItem))->assertRedirect(); assertSoftDeleted('order_items', ['id' => $cartItem->id]); }); it('can store a new order', function () { $product = Product::factory()->create(['stock' => 10]); // Update existing PURCHASE price from factory to ensure COGS calculation is predictable $product->prices()->where('price_type', PriceType::PURCHASE->value)->update([ 'price' => 8000, ]); $data = [ 'customer_name' => 'John Doe', 'subtotal' => 20000, 'discount' => 2000, 'payment' => 18000, 'payment_method' => PaymentMethod::CASH->value, 'order_status' => OrderStatus::PENDING->value, 'order_channel' => OrderChannel::STORE->value, 'items' => [ [ 'product_id' => $product->id, 'qty' => 2, 'price' => 10000, 'total' => 20000, 'price_type' => PriceType::RETAIL->value, ], ], ]; postJson(route('order.store'), $data) ->assertRedirect(route('order.index')) ->assertSessionHas('success'); assertDatabaseHas('orders', [ 'customer_name' => 'John Doe', 'subtotal' => 20000, 'discount' => 2000, 'total' => 18000, 'payment' => 18000, 'payment_method' => PaymentMethod::CASH->value, 'order_status' => OrderStatus::PENDING->value, 'order_channel' => OrderChannel::STORE->value, 'cogs' => 16000, // 8000 * 2 ]); // Check stock decrement expect($product->fresh()->stock)->toBe(8); // Check order items assertDatabaseHas('order_items', [ 'product_id' => $product->id, 'qty' => 2, 'price' => 10000, ]); }); it('validates order creation', function () { postJson(route('order.store'), []) ->assertStatus(422) ->assertJsonValidationErrors(['customer_name', 'items']); }); it('returns flash error when stock is insufficient on order store', function () { $product = Product::factory()->create(['stock' => 1]); $data = [ 'customer_name' => 'John Doe', 'subtotal' => 20000, 'discount' => 0, 'payment' => 20000, 'payment_method' => PaymentMethod::CASH->value, 'order_status' => OrderStatus::PENDING->value, 'order_channel' => OrderChannel::STORE->value, 'items' => [ [ 'product_id' => $product->id, 'qty' => 5, // lebih dari stok 'price' => 10000, 'total' => 50000, 'price_type' => PriceType::RETAIL->value, ], ], ]; postJson(route('order.store'), $data) ->assertRedirect() ->assertSessionHas('error'); expect($product->fresh()->stock)->toBe(1); }); it('returns flash error when stock is insufficient on order update', function () { $product = Product::factory()->create(['stock' => 2]); $order = Order::factory()->create(); $newData = [ 'customer_name' => 'Updated Customer', 'subtotal' => 30000, 'discount' => 0, 'payment' => 30000, 'payment_method' => PaymentMethod::CASH->value, 'order_status' => OrderStatus::PROCESSING->value, 'order_channel' => OrderChannel::STORE->value, 'items' => [ [ 'product_id' => $product->id, 'qty' => 10, // lebih dari stok 'price' => 3000, 'total' => 30000, 'price_type' => PriceType::RETAIL->value, ], ], ]; patchJson(route('order.update', $order), $newData) ->assertRedirect() ->assertSessionHas('error'); expect($product->fresh()->stock)->toBe(2); }); it('can update an order', function () { $product1 = Product::factory()->create(['stock' => 10]); $product2 = Product::factory()->create(['stock' => 10]); $order = Order::create([ 'customer_name' => 'Old Customer', 'cogs' => 0, 'subtotal' => 10000, 'discount' => 0, 'payment' => 10000, 'total' => 10000, 'payment_method' => PaymentMethod::CASH->value, 'order_status' => OrderStatus::PENDING->value, 'order_channel' => OrderChannel::STORE->value, ]); $order->items()->create([ 'user_id' => auth()->id(), 'product_id' => $product1->id, 'qty' => 1, 'price' => 10000, 'total' => 10000, 'price_type' => PriceType::RETAIL->value, ]); // Initial stock check $product1->decrement('stock', 1); $newData = [ 'customer_name' => 'Updated Customer', 'subtotal' => 15000, 'discount' => 0, 'payment' => 15000, 'payment_method' => PaymentMethod::CASH->value, 'order_status' => OrderStatus::PROCESSING->value, 'order_channel' => OrderChannel::STORE->value, 'items' => [ [ 'product_id' => $product2->id, 'qty' => 3, 'price' => 5000, 'total' => 15000, 'price_type' => PriceType::RETAIL->value, ], ], ]; patchJson(route('order.update', $order), $newData) ->assertRedirect(route('order.index')) ->assertSessionHas('success'); // Check data updated assertDatabaseHas('orders', [ 'id' => $order->id, 'customer_name' => 'Updated Customer', 'order_status' => OrderStatus::PROCESSING->value, 'payment' => 15000, 'total' => 15000, ]); // Check stock: product1 should be restored (10), product2 should be decremented (7) expect($product1->fresh()->stock)->toBe(10); expect($product2->fresh()->stock)->toBe(7); }); it('restores stock and preserves items when order status is changed to gagal', function () { $product = Product::factory()->create(['stock' => 10]); $order = Order::factory()->create(['order_status' => OrderStatus::PENDING->value]); $item = $order->items()->create([ 'user_id' => auth()->id(), 'product_id' => $product->id, 'qty' => 3, 'price' => 1000, 'total' => 3000, 'price_type' => PriceType::RETAIL->value, ]); $product->decrement('stock', 3); expect($product->fresh()->stock)->toBe(7); $newData = [ 'customer_name' => $order->customer_name, 'subtotal' => 3000, 'discount' => 0, 'payment' => 3000, 'payment_method' => $order->payment_method, 'order_status' => OrderStatus::CANCELLED->value, // Change to Gagal 'order_channel' => $order->order_channel, 'items' => [ // Even if we send items, they shouldn't be deleted/updated based on my early return [ 'product_id' => $product->id, 'qty' => 5, // different qty 'price' => 1000, 'total' => 5000, 'price_type' => PriceType::RETAIL->value, ], ], ]; patchJson(route('order.update', $order), $newData) ->assertRedirect(); // Stock should be restored (7 + 3 = 10) expect($product->fresh()->stock)->toBe(10); // Items should be preserved (original item with qty 3, NOT deleted/recreated with qty 5) expect($order->items()->count())->toBe(1); expect($order->items()->first()->qty)->toBe(3); expect($order->items()->first()->id)->toBe($item->id); // Same ID means not deleted/recreated }); it('can delete an order and restore stock', function () { $product = Product::factory()->create(['stock' => 5]); $order = Order::factory()->create(); $order->items()->create([ 'user_id' => auth()->id(), 'product_id' => $product->id, 'qty' => 2, 'price' => 1000, 'total' => 2000, 'price_type' => PriceType::RETAIL->value, ]); deleteJson(route('order.destroy', $order)) ->assertRedirect() ->assertSessionHas('success'); assertSoftDeleted('orders', ['id' => $order->id]); // Stock should be restored (5 + 2 = 7) expect($product->fresh()->stock)->toBe(7); }); it('can delete orders in bulk', function () { $orders = Order::factory()->count(3)->create(); $ids = $orders->pluck('id')->toArray(); deleteJson(route('order.bulkDestroy'), ['ids' => $ids]) ->assertRedirect() ->assertSessionHas('success'); foreach ($ids as $id) { assertSoftDeleted('orders', ['id' => $id]); } }); }); describe('Order Module - Unauthorized Actions', function () { beforeEach(function () { actingAs(createUnauthorizedUser()); }); it('cannot view an order without permission', function () { $order = Order::factory()->create(); get(route('order.show', $order)) ->assertStatus(403); }); it('cannot store an order without permission', function () { postJson(route('order.store'), ['customer_name' => 'Unauthorized']) ->assertStatus(403); }); it('cannot update an order without permission', function () { $order = Order::factory()->create(); patchJson(route('order.update', $order), ['customer_name' => 'Unauthorized']) ->assertStatus(403); }); it('cannot delete an order without permission', function () { $order = Order::factory()->create(); deleteJson(route('order.destroy', $order)) ->assertStatus(403); }); });