- Updated the OwnerVerificationRequest model to change pendingToggleIsActive to pendingToggleStatus, reflecting the new status structure. - Refactored the Product model to replace is_active with status, utilizing the ProductStatus enum for better clarity and type safety. - Modified the ProductService to accommodate the new status field, including pagination and creation logic. - Adjusted the AnalysisService to filter products based on their status. - Updated the VerificationChangeFormatter to handle the new status field. - Created a migration to remove is_active from the products table and add status with appropriate default values. - Refactored the ProductSeeder to use the new status field. - Updated frontend components and types to reflect the changes from is_active to status. - Adjusted tests to ensure they validate the new status logic correctly.
670 lines
24 KiB
PHP
670 lines
24 KiB
PHP
<?php
|
|
|
|
use App\Enums\OrderChannel;
|
|
use App\Enums\OrderStatus;
|
|
use App\Enums\PaymentType;
|
|
use App\Enums\Permission as PermissionEnum;
|
|
use App\Enums\PriceType;
|
|
use App\Enums\ProductStatus;
|
|
use App\Models\Customer;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\Product;
|
|
use App\Models\ProductPrice;
|
|
use App\Models\ProductVariant;
|
|
use App\Models\User;
|
|
use App\Services\Manage\OrderService;
|
|
use Database\Seeders\CashAccountSeeder;
|
|
use Database\Seeders\RolePermissionSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
$this->seed(RolePermissionSeeder::class);
|
|
User::factory()->create();
|
|
$this->seed(CashAccountSeeder::class);
|
|
Storage::fake('public');
|
|
});
|
|
|
|
// ─── Helper ───────────────────────────────────────────────
|
|
|
|
function createOrderUserWithPermission(PermissionEnum ...$permissions): User
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$user->givePermissionTo(
|
|
array_merge(
|
|
[PermissionEnum::DASHBOARD_VIEW->value],
|
|
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
|
|
)
|
|
);
|
|
|
|
$user->forgetCachedPermissions();
|
|
|
|
return $user;
|
|
}
|
|
|
|
function createOrderWithItems(?User $user = null): Order
|
|
{
|
|
$user ??= User::factory()->create();
|
|
|
|
$order = Order::factory()->create([
|
|
'created_by_id' => $user->id,
|
|
'channel' => OrderChannel::STORE->value,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'payment_type' => PaymentType::CASH->value,
|
|
]);
|
|
|
|
$product = Product::factory()->create();
|
|
$variant = ProductVariant::factory()->create([
|
|
'product_id' => $product->id,
|
|
'stock' => 100,
|
|
]);
|
|
|
|
OrderItem::factory()->create([
|
|
'order_id' => $order->id,
|
|
'product_variant_id' => $variant->id,
|
|
'quantity' => 5,
|
|
'unit_price' => 50000,
|
|
'subtotal' => 250000,
|
|
]);
|
|
|
|
return $order;
|
|
}
|
|
|
|
function setupOrderDraftItems(User $user): ProductVariant
|
|
{
|
|
$product = Product::factory()->create();
|
|
$variant = ProductVariant::factory()->create([
|
|
'product_id' => $product->id,
|
|
'stock' => 100,
|
|
]);
|
|
|
|
// Create product price so CuttingResultPriceResolver can resolve it
|
|
ProductPrice::factory()->create([
|
|
'variant_id' => $variant->id,
|
|
'type' => PriceType::RETAIL,
|
|
'price' => 50000,
|
|
]);
|
|
|
|
// Create draft item via API
|
|
test()->actingAs($user)
|
|
->postJson(route('admin.manage.orders.draft_items.store'), [
|
|
'product_variant_id' => $variant->id,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'stock_quality' => 'good',
|
|
'quantity' => 5,
|
|
])
|
|
->assertOk();
|
|
|
|
return $variant;
|
|
}
|
|
|
|
// ─── Index ────────────────────────────────────────────────
|
|
|
|
describe('Order Index', function () {
|
|
test('authenticated user with permission can view order index', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.index'))
|
|
->assertOk();
|
|
});
|
|
|
|
test('guest is redirected to login', function () {
|
|
$this->get(route('admin.manage.orders.index'))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
|
|
test('user without permission is forbidden', function () {
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.index'))
|
|
->assertForbidden();
|
|
});
|
|
|
|
test('index displays orders', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
createOrderWithItems($user);
|
|
createOrderWithItems($user);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.index'))
|
|
->assertOk();
|
|
});
|
|
|
|
test('index can search orders by order number', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
$order = Order::factory()->create([
|
|
'created_by_id' => $user->id,
|
|
'order_number' => 'ORD-20260704-TEST',
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.index', ['search' => 'TEST']))
|
|
->assertOk();
|
|
});
|
|
});
|
|
|
|
// ─── Create ───────────────────────────────────────────────
|
|
|
|
describe('Order Create', function () {
|
|
test('authenticated user with permission can view create form', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CREATE);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.create'))
|
|
->assertOk();
|
|
});
|
|
|
|
test('guest is redirected to login', function () {
|
|
$this->get(route('admin.manage.orders.create'))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
|
|
test('user without create permission is forbidden', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.create'))
|
|
->assertForbidden();
|
|
});
|
|
});
|
|
|
|
// ─── Store ────────────────────────────────────────────────
|
|
|
|
describe('Order Store', function () {
|
|
test('authenticated user with permission can create an order', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CREATE);
|
|
|
|
$customer = Customer::factory()->create();
|
|
setupOrderDraftItems($user);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.store'), [
|
|
'customer_id' => $customer->id,
|
|
'channel' => OrderChannel::STORE->value,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'payment_type' => PaymentType::CASH->value,
|
|
'discount' => 0,
|
|
])
|
|
->assertRedirect(route('admin.manage.orders.index'));
|
|
|
|
$order = Order::query()->latest()->first();
|
|
expect($order)->not->toBeNull();
|
|
expect($order->status)->toBe(OrderStatus::PENDING);
|
|
expect($order->items)->toHaveCount(1);
|
|
});
|
|
|
|
test('guest cannot create an order', function () {
|
|
$this->post(route('admin.manage.orders.store'), [
|
|
'channel' => OrderChannel::STORE->value,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'payment_type' => PaymentType::CASH->value,
|
|
])->assertRedirect(route('login'));
|
|
});
|
|
|
|
test('user without create permission is forbidden', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.store'), [
|
|
'channel' => OrderChannel::STORE->value,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'payment_type' => PaymentType::CASH->value,
|
|
])
|
|
->assertForbidden();
|
|
});
|
|
|
|
test('store fails without draft items', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CREATE);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.store'), [
|
|
'channel' => OrderChannel::STORE->value,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'payment_type' => PaymentType::CASH->value,
|
|
'discount' => 0,
|
|
])
|
|
->assertSessionHasErrors('items');
|
|
});
|
|
|
|
test('creating order decrements stock', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CREATE);
|
|
|
|
$variant = setupOrderDraftItems($user);
|
|
$initialStock = $variant->stock;
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.store'), [
|
|
'channel' => OrderChannel::STORE->value,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'payment_type' => PaymentType::CASH->value,
|
|
'discount' => 0,
|
|
]);
|
|
|
|
expect($variant->fresh()->stock)->toBe($initialStock - 5);
|
|
});
|
|
|
|
test('cashier creates order automatically sets status to completed and unsets marketing and customer', function () {
|
|
$user = User::factory()->create();
|
|
$user->assignRole('cashier');
|
|
$user->givePermissionTo(PermissionEnum::ORDERS_CREATE->value);
|
|
$user->forgetCachedPermissions();
|
|
|
|
setupOrderDraftItems($user);
|
|
|
|
$marketing = User::factory()->create();
|
|
$customer = Customer::factory()->create();
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.store'), [
|
|
'customer_id' => $customer->id,
|
|
'marketing_id' => $marketing->id,
|
|
'channel' => OrderChannel::STORE->value,
|
|
'price_type' => PriceType::RETAIL->value,
|
|
'payment_type' => PaymentType::CASH->value,
|
|
'status' => OrderStatus::PENDING->value,
|
|
'discount' => 0,
|
|
])
|
|
->assertRedirect(route('admin.manage.orders.index'));
|
|
|
|
$order = Order::query()->latest()->first();
|
|
expect($order)->not->toBeNull();
|
|
expect($order->status)->toBe(OrderStatus::COMPLETED);
|
|
expect($order->marketing_id)->toBeNull();
|
|
expect($order->customer_id)->toBeNull();
|
|
});
|
|
});
|
|
|
|
// ─── Show ─────────────────────────────────────────────────
|
|
|
|
describe('Order Show', function () {
|
|
test('authenticated user with permission can view order details', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
$order = createOrderWithItems($user);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.show', $order))
|
|
->assertOk();
|
|
});
|
|
|
|
test('guest is redirected to login', function () {
|
|
$order = createOrderWithItems();
|
|
|
|
$this->get(route('admin.manage.orders.show', $order))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
});
|
|
|
|
// ─── Edit ─────────────────────────────────────────────────
|
|
|
|
describe('Order Edit', function () {
|
|
test('authenticated user with permission can view edit form', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_UPDATE);
|
|
|
|
$order = createOrderWithItems($user);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.edit', $order))
|
|
->assertOk();
|
|
});
|
|
|
|
test('guest is redirected to login', function () {
|
|
$order = createOrderWithItems();
|
|
|
|
$this->get(route('admin.manage.orders.edit', $order))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
|
|
test('user without update permission is forbidden', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
$order = createOrderWithItems($user);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.edit', $order))
|
|
->assertForbidden();
|
|
});
|
|
|
|
test('completed order cannot be edited', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_UPDATE);
|
|
|
|
$order = Order::factory()->completed()->create(['created_by_id' => $user->id]);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('admin.manage.orders.edit', $order))
|
|
->assertRedirect(route('admin.manage.orders.index'));
|
|
});
|
|
});
|
|
|
|
// ─── Destroy ──────────────────────────────────────────────
|
|
|
|
describe('Order Destroy', function () {
|
|
test('authenticated user with permission can delete an order', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_DELETE);
|
|
|
|
$order = createOrderWithItems($user);
|
|
|
|
$this->actingAs($user)
|
|
->delete(route('admin.manage.orders.destroy', $order))
|
|
->assertRedirect(route('admin.manage.orders.index'));
|
|
|
|
$this->assertSoftDeleted('orders', ['id' => $order->id]);
|
|
});
|
|
|
|
test('guest cannot delete an order', function () {
|
|
$order = createOrderWithItems();
|
|
|
|
$this->delete(route('admin.manage.orders.destroy', $order))
|
|
->assertRedirect(route('login'));
|
|
|
|
$this->assertNotSoftDeleted('orders', ['id' => $order->id]);
|
|
});
|
|
|
|
test('user without delete permission is forbidden', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW);
|
|
|
|
$order = createOrderWithItems($user);
|
|
|
|
$this->actingAs($user)
|
|
->delete(route('admin.manage.orders.destroy', $order))
|
|
->assertForbidden();
|
|
|
|
$this->assertNotSoftDeleted('orders', ['id' => $order->id]);
|
|
});
|
|
|
|
test('deleting order increments stock back', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_DELETE);
|
|
|
|
$order = createOrderWithItems($user);
|
|
$item = $order->items->first();
|
|
$variant = $item->productVariant;
|
|
$stockBeforeDelete = $variant->fresh()->stock;
|
|
|
|
$this->actingAs($user)
|
|
->delete(route('admin.manage.orders.destroy', $order));
|
|
|
|
expect($variant->fresh()->stock)->toBe($stockBeforeDelete + $item->quantity);
|
|
});
|
|
});
|
|
|
|
// ─── Status Transition ────────────────────────────────────
|
|
|
|
describe('Order Status Transition', function () {
|
|
test('authenticated user can send an order', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_SEND);
|
|
|
|
$order = Order::factory()->create([
|
|
'created_by_id' => $user->id,
|
|
'status' => OrderStatus::PENDING->value,
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.transition_status', $order), [
|
|
'status' => OrderStatus::PROCESSING->value,
|
|
])
|
|
->assertRedirect(route('admin.manage.orders.index'));
|
|
|
|
expect($order->fresh()->status)->toBe(OrderStatus::PROCESSING);
|
|
});
|
|
|
|
test('authenticated user can complete an order', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_COMPLETE);
|
|
|
|
$order = Order::factory()->create([
|
|
'created_by_id' => $user->id,
|
|
'status' => OrderStatus::PROCESSING->value,
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.transition_status', $order), [
|
|
'status' => OrderStatus::COMPLETED->value,
|
|
]);
|
|
|
|
expect($order->fresh()->status)->toBe(OrderStatus::COMPLETED);
|
|
});
|
|
|
|
test('authenticated user can cancel an order', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CANCEL);
|
|
|
|
$order = Order::factory()->create([
|
|
'created_by_id' => $user->id,
|
|
'status' => OrderStatus::PENDING->value,
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.transition_status', $order), [
|
|
'status' => OrderStatus::CANCELLED->value,
|
|
]);
|
|
|
|
expect($order->fresh()->status)->toBe(OrderStatus::CANCELLED);
|
|
});
|
|
|
|
test('guest cannot transition status', function () {
|
|
$order = Order::factory()->create(['status' => OrderStatus::PENDING->value]);
|
|
|
|
$this->post(route('admin.manage.orders.transition_status', $order), [
|
|
'status' => OrderStatus::PROCESSING->value,
|
|
])->assertRedirect(route('login'));
|
|
});
|
|
|
|
test('invalid status transition is rejected', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_COMPLETE);
|
|
|
|
$order = Order::factory()->create([
|
|
'created_by_id' => $user->id,
|
|
'status' => OrderStatus::PENDING->value,
|
|
]);
|
|
|
|
// PENDING cannot go directly to COMPLETED
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.transition_status', $order), [
|
|
'status' => OrderStatus::COMPLETED->value,
|
|
])
|
|
->assertSessionHasErrors('status');
|
|
});
|
|
|
|
test('cancelling order increments stock back', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CANCEL);
|
|
|
|
$order = createOrderWithItems($user);
|
|
$item = $order->items->first();
|
|
$variant = $item->productVariant;
|
|
$stockBeforeCancel = $variant->fresh()->stock;
|
|
|
|
$this->actingAs($user)
|
|
->post(route('admin.manage.orders.transition_status', $order), [
|
|
'status' => OrderStatus::CANCELLED->value,
|
|
]);
|
|
|
|
expect($variant->fresh()->stock)->toBe($stockBeforeCancel + $item->quantity);
|
|
});
|
|
});
|
|
|
|
// ─── Order Model ──────────────────────────────────────────
|
|
|
|
describe('Order Model', function () {
|
|
test('order uses soft deletes', function () {
|
|
$order = Order::factory()->create();
|
|
|
|
$order->delete();
|
|
|
|
expect($order->trashed())->toBeTrue();
|
|
});
|
|
|
|
test('order has amounts cast to integer', function () {
|
|
$order = Order::factory()->create([
|
|
'subtotal' => 100000,
|
|
'discount' => 5000,
|
|
'total_amount' => 95000,
|
|
]);
|
|
|
|
expect($order->subtotal)->toBeInt();
|
|
expect($order->discount)->toBeInt();
|
|
expect($order->total_amount)->toBeInt();
|
|
});
|
|
|
|
test('order has formatted amount accessors', function () {
|
|
$order = Order::factory()->create([
|
|
'subtotal' => 100000,
|
|
'discount' => 5000,
|
|
'total_amount' => 95000,
|
|
]);
|
|
|
|
expect($order->subtotal_formatted)->toBe('Rp 100.000');
|
|
expect($order->discount_formatted)->toBe('Rp 5.000');
|
|
expect($order->total_amount_formatted)->toBe('Rp 95.000');
|
|
});
|
|
|
|
test('order has status cast to enum', function () {
|
|
$order = Order::factory()->create(['status' => OrderStatus::PENDING->value]);
|
|
|
|
expect($order->status)->toBe(OrderStatus::PENDING);
|
|
});
|
|
|
|
test('order has status label accessor', function () {
|
|
$order = Order::factory()->create(['status' => OrderStatus::PENDING->value]);
|
|
|
|
expect($order->status_label)->toBe('Menunggu');
|
|
});
|
|
|
|
test('order belongs to customer', function () {
|
|
$customer = Customer::factory()->create();
|
|
$order = Order::factory()->create(['customer_id' => $customer->id]);
|
|
|
|
expect($order->customer)->not->toBeNull();
|
|
expect($order->customer->id)->toBe($customer->id);
|
|
});
|
|
|
|
test('order belongs to created by user', function () {
|
|
$user = User::factory()->create();
|
|
$order = Order::factory()->create(['created_by_id' => $user->id]);
|
|
|
|
expect($order->createdBy)->not->toBeNull();
|
|
expect($order->createdBy->id)->toBe($user->id);
|
|
});
|
|
|
|
test('order can have items', function () {
|
|
$order = Order::factory()->create();
|
|
OrderItem::factory()->count(3)->create(['order_id' => $order->id]);
|
|
|
|
expect($order->fresh()->items)->toHaveCount(3);
|
|
});
|
|
|
|
test('order status is editable for pending and processing', function () {
|
|
$pending = Order::factory()->create(['status' => OrderStatus::PENDING->value]);
|
|
$processing = Order::factory()->create(['status' => OrderStatus::PROCESSING->value]);
|
|
|
|
expect($pending->status->isEditable())->toBeTrue();
|
|
expect($processing->status->isEditable())->toBeTrue();
|
|
});
|
|
});
|
|
|
|
// ─── OrderItem Model ──────────────────────────────────────
|
|
|
|
describe('OrderItem Model', function () {
|
|
test('order item has amounts cast to integer', function () {
|
|
$order = Order::factory()->create();
|
|
$item = OrderItem::factory()->create([
|
|
'order_id' => $order->id,
|
|
'quantity' => 10,
|
|
'unit_price' => 50000,
|
|
'subtotal' => 500000,
|
|
]);
|
|
|
|
expect($item->quantity)->toBeInt();
|
|
expect($item->unit_price)->toBeInt();
|
|
expect($item->subtotal)->toBeInt();
|
|
});
|
|
|
|
test('order item has formatted amount accessors', function () {
|
|
$order = Order::factory()->create();
|
|
$item = OrderItem::factory()->create([
|
|
'order_id' => $order->id,
|
|
'quantity' => 10,
|
|
'unit_price' => 50000,
|
|
'subtotal' => 500000,
|
|
]);
|
|
|
|
expect($item->quantity_formatted)->toBe('10 pcs');
|
|
expect($item->unit_price_formatted)->toBe('Rp 50.000');
|
|
expect($item->subtotal_formatted)->toBe('Rp 500.000');
|
|
});
|
|
|
|
test('order item belongs to order', function () {
|
|
$order = Order::factory()->create();
|
|
$item = OrderItem::factory()->create(['order_id' => $order->id]);
|
|
|
|
expect($item->order)->not->toBeNull();
|
|
expect($item->order->id)->toBe($order->id);
|
|
});
|
|
|
|
test('order item uses soft deletes', function () {
|
|
$order = Order::factory()->create();
|
|
$item = OrderItem::factory()->create(['order_id' => $order->id]);
|
|
|
|
$item->delete();
|
|
|
|
expect($item->trashed())->toBeTrue();
|
|
});
|
|
});
|
|
|
|
// ─── Order Catalog Stock Filter ─────────────────────────────
|
|
|
|
describe('Order Catalog Stock Filter', function () {
|
|
test('catalogItems excludes variants where all stock types are zero', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CREATE);
|
|
|
|
$product = Product::factory()->create(['status' => ProductStatus::ACTIVE]);
|
|
|
|
$variantWithStock = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 10, 'reject_stock' => 0, 'retail_stock' => 0]);
|
|
$variantAllZero = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 0, 'reject_stock' => 0, 'retail_stock' => 0]);
|
|
$variantRejectOnly = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 0, 'reject_stock' => 5, 'retail_stock' => 0]);
|
|
$variantRetailOnly = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 0, 'reject_stock' => 0, 'retail_stock' => 3]);
|
|
|
|
$service = app(OrderService::class);
|
|
$catalog = $service->catalogItems(user: $user);
|
|
$found = $catalog->firstWhere('id', $product->id);
|
|
|
|
$variantIds = $found?->variants->pluck('id')->all() ?? [];
|
|
|
|
expect($variantIds)->toContain($variantWithStock->id);
|
|
expect($variantIds)->toContain($variantRejectOnly->id);
|
|
expect($variantIds)->toContain($variantRetailOnly->id);
|
|
expect($variantIds)->not->toContain($variantAllZero->id);
|
|
});
|
|
|
|
test('catalogItems includes all-zero-stock variants already in order', function () {
|
|
$user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_UPDATE);
|
|
|
|
$product = Product::factory()->create(['status' => ProductStatus::ACTIVE]);
|
|
$variantAllZero = ProductVariant::factory()->create([
|
|
'product_id' => $product->id,
|
|
'stock' => 0,
|
|
'reject_stock' => 0,
|
|
'retail_stock' => 0,
|
|
]);
|
|
|
|
$order = Order::factory()->create(['created_by_id' => $user->id]);
|
|
OrderItem::factory()->create([
|
|
'order_id' => $order->id,
|
|
'product_variant_id' => $variantAllZero->id,
|
|
]);
|
|
|
|
$service = app(OrderService::class);
|
|
$catalog = $service->catalogItems($order);
|
|
$found = $catalog->firstWhere('id', $product->id);
|
|
|
|
$variantIds = $found?->variants->pluck('id')->all() ?? [];
|
|
|
|
expect($variantIds)->toContain($variantAllZero->id);
|
|
});
|
|
});
|