418 lines
13 KiB
PHP
418 lines
13 KiB
PHP
<?php
|
|
|
|
use App\Enums\Gender;
|
|
use App\Enums\PaymentStatus;
|
|
use App\Enums\VoucherType;
|
|
use App\Livewire\Studio\Manage\Order\Create;
|
|
use App\Models\Bottle;
|
|
use App\Models\Formula;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\Perfume;
|
|
use App\Models\Product;
|
|
use App\Models\User;
|
|
use App\Models\Voucher;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Livewire\Livewire;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
|
|
beforeEach(function () {
|
|
$this->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 aggregated to 2 (since bottle isn't a perfume and logic allows increment)
|
|
$this->assertDatabaseHas('order_items', [
|
|
'orderable_id' => $bottle->id,
|
|
'quantity' => 2,
|
|
]);
|
|
|
|
$this->assertDatabaseCount('order_items', 3); // 2 perfumes (diff quality) + 1 bottle (aggregated)
|
|
});
|
|
|
|
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();
|
|
$customer = $memberUser->customer()->create([
|
|
'name' => 'Voucher User',
|
|
'gender' => Gender::MALE->value,
|
|
]);
|
|
$memberUser->membership()->create([
|
|
'tier_id' => $this->tier->id,
|
|
'total_spending' => 0,
|
|
'reward_points' => 0,
|
|
]);
|
|
|
|
$voucher = Voucher::factory()->create([
|
|
'type' => VoucherType::FIXED->value,
|
|
'discount_amount' => 20000,
|
|
'start_date' => now()->subDay()->format('Y-m-d'), // Make it active
|
|
]);
|
|
|
|
// Assign voucher to user and outlet
|
|
DB::table('user_voucher')->insert([
|
|
'user_id' => $memberUser->id,
|
|
'voucher_id' => $voucher->id,
|
|
'quantity' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
$voucher->outlets()->attach($this->outlet->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)
|
|
->set('form.voucher_id', $voucher->id)
|
|
->set('form.payment_method', '1') // Cash
|
|
->call('save')
|
|
->assertHasNoErrors()
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('orders', [
|
|
'voucher_id' => $voucher->id,
|
|
'total' => 80000,
|
|
'discount' => 0, // Manual discount
|
|
'voucher_discount' => 20000, // Voucher discount
|
|
]);
|
|
|
|
// 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();
|
|
});
|