342 lines
12 KiB
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(route('admin.manage.owner_verifications.index'));
|
|
|
|
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(route('admin.manage.owner_verifications.index'));
|
|
|
|
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)
|
|
);
|
|
});
|
|
});
|