store/tests/Feature/Admin/Manage/PurchaseTest.php

342 lines
12 KiB
PHP

<?php
use App\Enums\OwnerVerificationAction;
use App\Enums\OwnerVerificationStatus;
use App\Enums\Permission as PermissionEnum;
use App\Models\OwnerVerificationRequest;
use App\Models\Purchase;
use App\Models\PurchaseItem;
use App\Models\RawMaterialPrice;
use App\Models\Supplier;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
});
function createPurchaseUserWithPermission(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 createPurchaseVerifierUser(): User
{
$user = User::factory()->create();
$user->givePermissionTo([
PermissionEnum::DASHBOARD_VIEW->value,
PermissionEnum::OWNER_VERIFICATIONS_VIEW->value,
PermissionEnum::OWNER_VERIFICATIONS_VERIFY->value,
PermissionEnum::OWNER_VERIFICATIONS_REJECT->value,
]);
$user->forgetCachedPermissions();
return $user;
}
function approveLatestPurchaseVerificationRequest(User $verifier): OwnerVerificationRequest
{
$verificationRequest = OwnerVerificationRequest::query()->pending()->latest()->firstOrFail();
test()->actingAs($verifier)
->post(route('admin.manage.owner_verifications.approve_request', $verificationRequest))
->assertRedirect();
return $verificationRequest->fresh();
}
describe('Purchase Owner Verification', function () {
test('create purchase submits verification without changing stock', function () {
$user = createPurchaseUserWithPermission(
PermissionEnum::PURCHASES_VIEW,
PermissionEnum::PURCHASES_CREATE,
);
$supplier = Supplier::factory()->create();
$price = RawMaterialPrice::factory()->create(['stock' => 10]);
$initialStock = (float) $price->stock;
$this->actingAs($user)
->postJson(route('admin.manage.purchases.draft_items.store'), [
'raw_material_price_id' => $price->id,
'quantity' => 2,
])
->assertOk();
$this->actingAs($user)
->post(route('admin.manage.purchases.store'), [
'supplier_id' => $supplier->id,
'discount' => 0,
'shipping_cost' => 0,
'notes' => 'Belanja test',
])
->assertRedirect(route('admin.manage.purchases.index'));
$purchase = Purchase::query()->latest()->first();
expect($purchase)->not->toBeNull();
$this->assertDatabaseHas('owner_verification_requests', [
'subject_type' => Purchase::class,
'subject_id' => $purchase->id,
'action' => OwnerVerificationAction::CREATE->value,
'status' => OwnerVerificationStatus::PENDING->value,
]);
expect((float) $price->fresh()->stock)->toBe($initialStock);
});
test('approving create purchase increments stock', function () {
$user = createPurchaseUserWithPermission(
PermissionEnum::PURCHASES_VIEW,
PermissionEnum::PURCHASES_CREATE,
);
$verifier = createPurchaseVerifierUser();
$supplier = Supplier::factory()->create();
$price = RawMaterialPrice::factory()->create(['stock' => 10]);
$this->actingAs($user)
->postJson(route('admin.manage.purchases.draft_items.store'), [
'raw_material_price_id' => $price->id,
'quantity' => 2,
])
->assertOk();
$this->actingAs($user)
->post(route('admin.manage.purchases.store'), [
'supplier_id' => $supplier->id,
'discount' => 0,
'shipping_cost' => 0,
])
->assertRedirect(route('admin.manage.purchases.index'));
approveLatestPurchaseVerificationRequest($verifier);
expect((float) $price->fresh()->stock)->toBe(12.0);
});
test('update purchase submits verification request only', function () {
$user = createPurchaseUserWithPermission(
PermissionEnum::PURCHASES_VIEW,
PermissionEnum::PURCHASES_CREATE,
PermissionEnum::PURCHASES_UPDATE,
);
$verifier = createPurchaseVerifierUser();
$supplier = Supplier::factory()->create();
$price = RawMaterialPrice::factory()->create(['stock' => 10, 'price' => 50_000]);
$otherPrice = RawMaterialPrice::factory()->create(['stock' => 5, 'price' => 30_000]);
$this->actingAs($user)
->postJson(route('admin.manage.purchases.draft_items.store'), [
'raw_material_price_id' => $price->id,
'quantity' => 2,
])
->assertOk();
$this->actingAs($user)
->post(route('admin.manage.purchases.store'), [
'supplier_id' => $supplier->id,
'discount' => 0,
'shipping_cost' => 0,
])
->assertRedirect(route('admin.manage.purchases.index'));
approveLatestPurchaseVerificationRequest($verifier);
$purchase = Purchase::query()->latest()->firstOrFail();
$originalTotal = $purchase->total;
$this->actingAs($user)
->put(route('admin.manage.purchases.update', $purchase), [
'supplier_id' => $supplier->id,
'discount' => 1000,
'shipping_cost' => 0,
'notes' => 'Ubah belanja',
'items' => [
['raw_material_price_id' => $otherPrice->id, 'quantity' => 1],
],
])
->assertRedirect(route('admin.manage.purchases.index'));
$this->assertDatabaseHas('owner_verification_requests', [
'subject_type' => Purchase::class,
'subject_id' => $purchase->id,
'action' => OwnerVerificationAction::UPDATE->value,
'status' => OwnerVerificationStatus::PENDING->value,
]);
expect($purchase->fresh()->total)->toBe($originalTotal);
});
test('delete purchase submits verification request only', function () {
$user = createPurchaseUserWithPermission(
PermissionEnum::PURCHASES_VIEW,
PermissionEnum::PURCHASES_CREATE,
PermissionEnum::PURCHASES_DELETE,
);
$verifier = createPurchaseVerifierUser();
$supplier = Supplier::factory()->create();
$price = RawMaterialPrice::factory()->create(['stock' => 10]);
$this->actingAs($user)
->postJson(route('admin.manage.purchases.draft_items.store'), [
'raw_material_price_id' => $price->id,
'quantity' => 2,
])
->assertOk();
$this->actingAs($user)
->post(route('admin.manage.purchases.store'), [
'supplier_id' => $supplier->id,
'discount' => 0,
'shipping_cost' => 0,
])
->assertRedirect(route('admin.manage.purchases.index'));
approveLatestPurchaseVerificationRequest($verifier);
$purchase = Purchase::query()->latest()->firstOrFail();
$this->actingAs($user)
->delete(route('admin.manage.purchases.destroy', $purchase))
->assertRedirect(route('admin.manage.purchases.index'));
$this->assertDatabaseHas('owner_verification_requests', [
'subject_type' => Purchase::class,
'subject_id' => $purchase->id,
'action' => OwnerVerificationAction::DELETE->value,
'status' => OwnerVerificationStatus::PENDING->value,
]);
expect(Purchase::query()->whereKey($purchase->id)->exists())->toBeTrue();
expect((float) $price->fresh()->stock)->toBe(12.0);
});
test('rejecting create purchase deletes purchase without changing stock', function () {
$user = createPurchaseUserWithPermission(
PermissionEnum::PURCHASES_VIEW,
PermissionEnum::PURCHASES_CREATE,
);
$verifier = createPurchaseVerifierUser();
$supplier = Supplier::factory()->create();
$price = RawMaterialPrice::factory()->create(['stock' => 10]);
$this->actingAs($user)
->postJson(route('admin.manage.purchases.draft_items.store'), [
'raw_material_price_id' => $price->id,
'quantity' => 2,
])
->assertOk();
$this->actingAs($user)
->post(route('admin.manage.purchases.store'), [
'supplier_id' => $supplier->id,
'discount' => 0,
'shipping_cost' => 0,
])
->assertRedirect(route('admin.manage.purchases.index'));
$purchase = Purchase::query()->latest()->firstOrFail();
$verificationRequest = OwnerVerificationRequest::query()->pending()->latest()->firstOrFail();
$this->actingAs($verifier)
->post(route('admin.manage.owner_verifications.reject_request', $verificationRequest), [
'reason' => 'Tidak sesuai',
])
->assertRedirect();
expect(Purchase::query()->whereKey($purchase->id)->exists())->toBeFalse();
expect(PurchaseItem::query()->where('purchase_id', $purchase->id)->exists())->toBeFalse();
expect((float) $price->fresh()->stock)->toBe(10.0);
});
test('pending purchase blocks edit route', function () {
$user = createPurchaseUserWithPermission(
PermissionEnum::PURCHASES_VIEW,
PermissionEnum::PURCHASES_CREATE,
PermissionEnum::PURCHASES_UPDATE,
);
$supplier = Supplier::factory()->create();
$price = RawMaterialPrice::factory()->create(['stock' => 10]);
$this->actingAs($user)
->postJson(route('admin.manage.purchases.draft_items.store'), [
'raw_material_price_id' => $price->id,
'quantity' => 2,
])
->assertOk();
$this->actingAs($user)
->post(route('admin.manage.purchases.store'), [
'supplier_id' => $supplier->id,
'discount' => 0,
'shipping_cost' => 0,
])
->assertRedirect(route('admin.manage.purchases.index'));
$purchase = Purchase::query()->latest()->firstOrFail();
$this->actingAs($user)
->get(route('admin.manage.purchases.edit', $purchase))
->assertRedirect(route('admin.manage.purchases.index'));
});
test('purchase index exposes pending verification state', function () {
$user = createPurchaseUserWithPermission(
PermissionEnum::PURCHASES_VIEW,
PermissionEnum::PURCHASES_CREATE,
);
$supplier = Supplier::factory()->create();
$price = RawMaterialPrice::factory()->create(['stock' => 10]);
$this->actingAs($user)
->postJson(route('admin.manage.purchases.draft_items.store'), [
'raw_material_price_id' => $price->id,
'quantity' => 2,
])
->assertOk();
$this->actingAs($user)
->post(route('admin.manage.purchases.store'), [
'supplier_id' => $supplier->id,
'discount' => 0,
'shipping_cost' => 0,
])
->assertRedirect(route('admin.manage.purchases.index'));
$purchase = Purchase::query()->latest()->firstOrFail();
$response = $this->actingAs($user)
->get(route('admin.manage.purchases.index'));
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->where('purchases.data.0.id', $purchase->id)
->where('purchases.data.0.has_pending_request', true)
->where('purchases.data.0.pending_request_action', OwnerVerificationAction::CREATE->value)
);
});
});