seed(RolePermissionSeeder::class); }); function createOwnerVerificationRequestFor(User $submitter, ?Product $product = null): OwnerVerificationRequest { $product ??= Product::factory()->create(['is_active' => false]); return OwnerVerificationRequest::query()->create([ 'action' => OwnerVerificationAction::CREATE, 'status' => OwnerVerificationStatus::PENDING, 'subject_type' => Product::class, 'subject_id' => $product->id, 'submitted_by_id' => $submitter->id, 'payload' => ['old' => null, 'new' => ['name' => $product->name]], ]); } function createOwnerVerifierUser(): User { $user = User::factory()->create(); $user->assignRole(RoleEnum::OWNER->value); $user->forgetCachedPermissions(); return $user; } function createPendingCutting(?User $submitter = null): Cutting { $submitter ??= User::factory()->create(); return Cutting::factory()->create([ 'status' => CuttingStatus::PENDING_VERIFICATION, 'submitted_by_id' => $submitter->id, 'created_by_id' => $submitter->id, 'description' => 'Cutting test pending', ]); } describe('Owner Verification Index', function () { test('submitter with view permission only sees own verification requests', function () { $submitter = User::factory()->create(); $submitter->assignRole(RoleEnum::ADMIN_TOKO->value); $submitter->forgetCachedPermissions(); $otherUser = User::factory()->create(); $otherUser->assignRole(RoleEnum::ADMIN_TOKO->value); $otherUser->forgetCachedPermissions(); $ownRequest = createOwnerVerificationRequestFor($submitter); createOwnerVerificationRequestFor($otherUser); $response = $this->actingAs($submitter) ->get(route('admin.manage.owner_verifications.index')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('admin/manage/owner-verifications/Index') ->has('verificationRequests.data', 1) ->where('verificationRequests.data.0.id', $ownRequest->id) ->where('verificationRequests.data.0.source', 'request') ); }); test('verifier sees all verification requests', function () { $submitter = User::factory()->create(); $submitter->assignRole(RoleEnum::ADMIN_TOKO->value); $submitter->forgetCachedPermissions(); $verifier = createOwnerVerifierUser(); createOwnerVerificationRequestFor($submitter); createOwnerVerificationRequestFor($submitter); $response = $this->actingAs($verifier) ->get(route('admin.manage.owner_verifications.index')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('admin/manage/owner-verifications/Index') ->has('verificationRequests.data', 2) ); }); test('submitter cannot approve verification request', function () { $submitter = User::factory()->create(); $submitter->assignRole(RoleEnum::ADMIN_TOKO->value); $submitter->forgetCachedPermissions(); $request = createOwnerVerificationRequestFor($submitter); $this->actingAs($submitter) ->post(route('admin.manage.owner_verifications.approve_request', $request)) ->assertForbidden(); }); test('admin toko role has owner verification view permission', function () { $user = User::factory()->create(); $user->assignRole(RoleEnum::ADMIN_TOKO->value); $user->forgetCachedPermissions(); expect($user->can(PermissionEnum::OWNER_VERIFICATIONS_VIEW->value))->toBeTrue(); expect($user->can(PermissionEnum::OWNER_VERIFICATIONS_VERIFY->value))->toBeFalse(); }); test('admin bahan baku role has owner verification view permission', function () { $user = User::factory()->create(); $user->assignRole(RoleEnum::ADMIN_BAHAN_BAKU->value); $user->forgetCachedPermissions(); expect($user->can(PermissionEnum::OWNER_VERIFICATIONS_VIEW->value))->toBeTrue(); expect($user->can(PermissionEnum::OWNER_VERIFICATIONS_VERIFY->value))->toBeFalse(); }); }); describe('Owner Verification Unified Cutting List', function () { test('verifier sees pending cuttings in the same verification table', function () { $verifier = createOwnerVerifierUser(); $cutting = createPendingCutting(); $request = createOwnerVerificationRequestFor(User::factory()->create()); $response = $this->actingAs($verifier) ->get(route('admin.manage.owner_verifications.index')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('admin/manage/owner-verifications/Index') ->has('verificationRequests.data', 2) ->where('verificationRequests.data.0.source', 'cutting') ->where('verificationRequests.data.0.id', $cutting->id) ->where('verificationRequests.data.0.action', OwnerVerificationAction::STOCK_VERIFY->value) ->where('verificationRequests.data.0.subject_type', Cutting::class) ->where('verificationRequests.data.1.source', 'request') ->where('verificationRequests.data.1.id', $request->id) ); }); test('submitter does not see pending cuttings in verification table', function () { $submitter = User::factory()->create(); $submitter->assignRole(RoleEnum::ADMIN_TOKO->value); $submitter->forgetCachedPermissions(); createPendingCutting(); createOwnerVerificationRequestFor($submitter); $response = $this->actingAs($submitter) ->get(route('admin.manage.owner_verifications.index')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('admin/manage/owner-verifications/Index') ->has('verificationRequests.data', 1) ->where('verificationRequests.data.0.source', 'request') ); }); test('filtering by cutting module shows only pending cuttings', function () { $verifier = createOwnerVerifierUser(); $cutting = createPendingCutting(); createOwnerVerificationRequestFor(User::factory()->create()); $response = $this->actingAs($verifier) ->get(route('admin.manage.owner_verifications.index', [ 'subject_type' => Cutting::class, ])); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->has('verificationRequests.data', 1) ->where('verificationRequests.data.0.source', 'cutting') ->where('verificationRequests.data.0.id', $cutting->id) ); }); test('filtering by stock verify action shows only pending cuttings', function () { $verifier = createOwnerVerifierUser(); $cutting = createPendingCutting(); createOwnerVerificationRequestFor(User::factory()->create()); $response = $this->actingAs($verifier) ->get(route('admin.manage.owner_verifications.index', [ 'action' => OwnerVerificationAction::STOCK_VERIFY->value, ])); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->has('verificationRequests.data', 1) ->where('verificationRequests.data.0.source', 'cutting') ->where('verificationRequests.data.0.id', $cutting->id) ); }); test('filtering by approved status hides pending cuttings', function () { $verifier = createOwnerVerifierUser(); createPendingCutting(); OwnerVerificationRequest::query()->create([ 'action' => OwnerVerificationAction::CREATE, 'status' => OwnerVerificationStatus::APPROVED, 'subject_type' => Product::class, 'subject_id' => Product::factory()->create()->id, 'submitted_by_id' => User::factory()->create()->id, 'payload' => ['old' => null, 'new' => ['name' => 'Approved Product']], 'verified_by_id' => $verifier->id, 'verified_at' => now(), ]); $response = $this->actingAs($verifier) ->get(route('admin.manage.owner_verifications.index', [ 'status' => OwnerVerificationStatus::APPROVED->value, ])); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->has('verificationRequests.data', 1) ->where('verificationRequests.data.0.source', 'request') ->where('verificationRequests.data.0.status', OwnerVerificationStatus::APPROVED->value) ); }); test('subject options include cutting when pending cuttings exist', function () { $verifier = createOwnerVerifierUser(); createPendingCutting(); $response = $this->actingAs($verifier) ->get(route('admin.manage.owner_verifications.index')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->where('subjectOptions', fn ($options) => collect($options)->contains( fn (array $option) => $option['value'] === Cutting::class && $option['label'] === ModelLabel::for(Cutting::class), )) ); }); test('verifier can approve pending cutting from verification page', function () { $verifier = createOwnerVerifierUser(); $cutting = createPendingCutting(); $this->actingAs($verifier) ->post(route('admin.manage.owner_verifications.approve', $cutting)) ->assertRedirect(route('admin.manage.owner_verifications.index')); expect($cutting->fresh()->status)->toBe(CuttingStatus::VERIFIED); }); test('verifier can reject pending cutting from verification page', function () { $verifier = createOwnerVerifierUser(); $cutting = createPendingCutting(); $this->actingAs($verifier) ->post(route('admin.manage.owner_verifications.reject', $cutting), [ 'reason' => 'Data tidak sesuai', ]) ->assertRedirect(route('admin.manage.owner_verifications.index')); $cutting->refresh(); expect($cutting->status)->toBe(CuttingStatus::COMPLETED); expect($cutting->rejection?->reason)->toBe('Data tidak sesuai'); expect($cutting->rejection?->rejected_by_id)->toBe($verifier->id); }); }); describe('Owner Verification Purchase Module', function () { test('verifier sees purchase verification requests in index', function () { $verifier = createOwnerVerifierUser(); $submitter = User::factory()->create(); $purchase = Purchase::factory()->create(); OwnerVerificationRequest::query()->create([ 'action' => OwnerVerificationAction::CREATE, 'status' => OwnerVerificationStatus::PENDING, 'subject_type' => Purchase::class, 'subject_id' => $purchase->id, 'submitted_by_id' => $submitter->id, 'payload' => [ 'old' => null, 'new' => ['supplier_name' => $purchase->supplier?->name ?? 'Supplier'], ], ]); $response = $this->actingAs($verifier) ->get(route('admin.manage.owner_verifications.index', [ 'subject_type' => Purchase::class, ])); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->has('verificationRequests.data', 1) ->where('verificationRequests.data.0.source', 'request') ->where('verificationRequests.data.0.subject_type', Purchase::class) ->where('verificationRequests.data.0.subject_id', $purchase->id) ); }); test('purchase submitter sees only own purchase verification requests', function () { $submitter = User::factory()->create(); $submitter->assignRole(RoleEnum::ADMIN_BAHAN_BAKU->value); $submitter->forgetCachedPermissions(); $otherUser = User::factory()->create(); $ownPurchase = Purchase::factory()->create(['created_by_id' => $submitter->id]); $otherPurchase = Purchase::factory()->create(['created_by_id' => $otherUser->id]); OwnerVerificationRequest::query()->create([ 'action' => OwnerVerificationAction::CREATE, 'status' => OwnerVerificationStatus::PENDING, 'subject_type' => Purchase::class, 'subject_id' => $ownPurchase->id, 'submitted_by_id' => $submitter->id, 'payload' => ['old' => null, 'new' => ['supplier_name' => 'Own']], ]); OwnerVerificationRequest::query()->create([ 'action' => OwnerVerificationAction::CREATE, 'status' => OwnerVerificationStatus::PENDING, 'subject_type' => Purchase::class, 'subject_id' => $otherPurchase->id, 'submitted_by_id' => $otherUser->id, 'payload' => ['old' => null, 'new' => ['supplier_name' => 'Other']], ]); $response = $this->actingAs($submitter) ->get(route('admin.manage.owner_verifications.index', [ 'subject_type' => Purchase::class, ])); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->has('verificationRequests.data', 1) ->where('verificationRequests.data.0.subject_id', $ownPurchase->id) ); }); });