- Updated AGENTS.md to clarify site admin roles and their associated permissions. - Refactored reconciliation controllers to include admin user validation and improved access control based on admin roles. - Enhanced AdminReconcileJobService to support player-specific reconciliation and site-based job creation. - Removed deprecated rebate commission report functionality from the API and related services. - Improved dashboard overview builders to accommodate new site operator roles and their specific functionalities.
212 lines
7.4 KiB
PHP
212 lines
7.4 KiB
PHP
<?php
|
|
|
|
use App\Models\AdminUser;
|
|
use App\Models\Player;
|
|
use App\Models\ReconcileJob;
|
|
use App\Models\TransferOrder;
|
|
use App\Support\SitePlatformRole;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Laravel\Sanctum\Sanctum;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function (): void {
|
|
$this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
|
|
});
|
|
|
|
function reconcileScopeSuperToken(): string
|
|
{
|
|
$admin = AdminUser::query()->create([
|
|
'username' => 'reconcile_scope_super',
|
|
'name' => 'Super',
|
|
'email' => null,
|
|
'password' => Hash::make('secret-strong'),
|
|
'status' => 0,
|
|
]);
|
|
grantSuperAdminRole($admin);
|
|
|
|
return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
|
}
|
|
|
|
test('reconcile job list is scoped to site admin site', function (): void {
|
|
$superToken = reconcileScopeSuperToken();
|
|
|
|
$siteA = $this->withHeader('Authorization', 'Bearer '.$superToken)
|
|
->postJson('/api/v1/admin/integration-sites', [
|
|
'code' => 'reconcile-a',
|
|
'name' => 'Reconcile A',
|
|
'admin_account' => [
|
|
'username' => 'reconcile_a_admin',
|
|
'nickname' => 'A Admin',
|
|
'password' => 'secret-strong',
|
|
],
|
|
])
|
|
->assertCreated()
|
|
->json('data');
|
|
|
|
$siteB = $this->withHeader('Authorization', 'Bearer '.$superToken)
|
|
->postJson('/api/v1/admin/integration-sites', [
|
|
'code' => 'reconcile-b',
|
|
'name' => 'Reconcile B',
|
|
'admin_account' => [
|
|
'username' => 'reconcile_b_admin',
|
|
'nickname' => 'B Admin',
|
|
'password' => 'secret-strong',
|
|
],
|
|
])
|
|
->assertCreated()
|
|
->json('data');
|
|
|
|
$playerA = Player::query()->create([
|
|
'site_code' => 'reconcile-a',
|
|
'site_player_id' => 'scope-a-1',
|
|
'username' => null,
|
|
'nickname' => null,
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
$playerB = Player::query()->create([
|
|
'site_code' => 'reconcile-b',
|
|
'site_player_id' => 'scope-b-1',
|
|
'username' => null,
|
|
'nickname' => null,
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
|
|
foreach ([
|
|
['TO_scope_a', $playerA->id, (int) $siteA['id']],
|
|
['TO_scope_b', $playerB->id, (int) $siteB['id']],
|
|
] as [$transferNo, $playerId, $siteId]) {
|
|
TransferOrder::query()->create([
|
|
'transfer_no' => $transferNo,
|
|
'player_id' => $playerId,
|
|
'direction' => 'out',
|
|
'currency_code' => 'NPR',
|
|
'amount' => 100,
|
|
'idempotent_key' => $transferNo.'-key',
|
|
'status' => 'pending_reconcile',
|
|
'external_request_payload' => null,
|
|
'external_response_payload' => null,
|
|
'external_ref_no' => null,
|
|
'fail_reason' => 'main_site_timeout',
|
|
'finished_at' => null,
|
|
'created_at' => now()->subHours(2),
|
|
]);
|
|
|
|
$this->withHeader('Authorization', 'Bearer '.$superToken)
|
|
->postJson('/api/v1/admin/reconcile-jobs', [
|
|
'reconcile_type' => 'wallet_transfer',
|
|
'date_from' => now()->subDay()->toDateString(),
|
|
'date_to' => now()->toDateString(),
|
|
'player_id' => $playerId,
|
|
])
|
|
->assertOk();
|
|
}
|
|
|
|
$jobA = ReconcileJob::query()->where('admin_site_id', (int) $siteA['id'])->latest('id')->first();
|
|
$jobB = ReconcileJob::query()->where('admin_site_id', (int) $siteB['id'])->latest('id')->first();
|
|
expect($jobA)->not->toBeNull()
|
|
->and($jobB)->not->toBeNull()
|
|
->and((int) $jobA->admin_site_id)->toBe((int) $siteA['id'])
|
|
->and((int) $jobB->admin_site_id)->toBe((int) $siteB['id']);
|
|
|
|
$siteAdminA = AdminUser::query()->where('username', 'reconcile_a_admin')->firstOrFail();
|
|
$siteAdminB = AdminUser::query()->where('username', 'reconcile_b_admin')->firstOrFail();
|
|
expect(SitePlatformRole::userHasSiteAdminRole($siteAdminA))->toBeTrue()
|
|
->and($siteAdminA->isSuperAdmin())->toBeFalse()
|
|
->and($siteAdminA->accessibleAdminSiteIds())->toBe([(int) $siteA['id']])
|
|
->and($siteAdminA->primaryAgentNode())->toBeNull()
|
|
->and($siteAdminB->accessibleAdminSiteIds())->toBe([(int) $siteB['id']]);
|
|
|
|
Sanctum::actingAs($siteAdminA, ['*']);
|
|
$idsA = collect($this->getJson('/api/v1/admin/reconcile-jobs')
|
|
->assertOk()
|
|
->json('data.items'))
|
|
->pluck('id')
|
|
->all();
|
|
|
|
Sanctum::actingAs($siteAdminB, ['*']);
|
|
$idsB = collect($this->getJson('/api/v1/admin/reconcile-jobs')
|
|
->assertOk()
|
|
->json('data.items'))
|
|
->pluck('id')
|
|
->all();
|
|
|
|
expect($idsA)->toEqual([(int) $jobA->id])
|
|
->and($idsB)->toEqual([(int) $jobB->id]);
|
|
|
|
Sanctum::actingAs($siteAdminA, ['*']);
|
|
$this->getJson('/api/v1/admin/reconcile-jobs/'.$jobB->id.'/items')
|
|
->assertForbidden();
|
|
});
|
|
|
|
test('site admin reconcile scan only includes own site transfer orders', function (): void {
|
|
$superToken = reconcileScopeSuperToken();
|
|
|
|
$this->withHeader('Authorization', 'Bearer '.$superToken)
|
|
->postJson('/api/v1/admin/integration-sites', [
|
|
'code' => 'scan-scope-a',
|
|
'name' => 'Scan Scope A',
|
|
'admin_account' => [
|
|
'username' => 'scan_scope_a_admin',
|
|
'nickname' => 'Scan A',
|
|
'password' => 'secret-strong',
|
|
],
|
|
])
|
|
->assertCreated();
|
|
|
|
$defaultSiteCode = (string) DB::table('admin_sites')->where('is_default', true)->value('code');
|
|
|
|
$ownPlayer = Player::query()->create([
|
|
'site_code' => 'scan-scope-a',
|
|
'site_player_id' => 'scan-own',
|
|
'username' => null,
|
|
'nickname' => null,
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
$otherPlayer = Player::query()->create([
|
|
'site_code' => $defaultSiteCode,
|
|
'site_player_id' => 'scan-other',
|
|
'username' => null,
|
|
'nickname' => null,
|
|
'default_currency' => 'NPR',
|
|
'status' => 0,
|
|
]);
|
|
|
|
foreach ([['TO_scan_own', $ownPlayer->id], ['TO_scan_other', $otherPlayer->id]] as [$transferNo, $playerId]) {
|
|
TransferOrder::query()->create([
|
|
'transfer_no' => $transferNo,
|
|
'player_id' => $playerId,
|
|
'direction' => 'out',
|
|
'currency_code' => 'NPR',
|
|
'amount' => 200,
|
|
'idempotent_key' => $transferNo.'-key',
|
|
'status' => 'pending_reconcile',
|
|
'external_request_payload' => null,
|
|
'external_response_payload' => null,
|
|
'external_ref_no' => null,
|
|
'fail_reason' => 'main_site_timeout',
|
|
'finished_at' => null,
|
|
'created_at' => now()->subHours(2),
|
|
]);
|
|
}
|
|
|
|
$siteAdmin = AdminUser::query()->where('username', 'scan_scope_a_admin')->firstOrFail();
|
|
Sanctum::actingAs($siteAdmin, ['*']);
|
|
|
|
$this->postJson('/api/v1/admin/reconcile-jobs', [
|
|
'reconcile_type' => 'wallet_transfer',
|
|
'date_from' => now()->subDay()->toDateString(),
|
|
'date_to' => now()->toDateString(),
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.item_count', 1);
|
|
|
|
$job = ReconcileJob::query()->latest('id')->firstOrFail();
|
|
expect($job->items()->value('side_a_ref'))->toBe('TO_scan_own');
|
|
});
|