|
@@ -6,6 +6,7 @@ use App\Services\Reporting\ReportAdmin;
|
|
|
use App\Services\Reporting\ReportReader;
|
|
use App\Services\Reporting\ReportReader;
|
|
|
use App\Services\Reporting\ReportSync;
|
|
use App\Services\Reporting\ReportSync;
|
|
|
use App\Services\Sharing\ShareDefaults;
|
|
use App\Services\Sharing\ShareDefaults;
|
|
|
|
|
+use App\Services\ThirdGameCommissionService;
|
|
|
use Carbon\Carbon;
|
|
use Carbon\Carbon;
|
|
|
use Illuminate\Config\Repository;
|
|
use Illuminate\Config\Repository;
|
|
|
use Illuminate\Database\Capsule\Manager;
|
|
use Illuminate\Database\Capsule\Manager;
|
|
@@ -65,6 +66,7 @@ class ReportingTest extends TestCase
|
|
|
'operation_audits' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,resource_type TEXT,resource_id INTEGER,action TEXT,changes TEXT,operator_id INTEGER,operator_name TEXT,created_at TEXT DEFAULT CURRENT_TIMESTAMP',
|
|
'operation_audits' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,resource_type TEXT,resource_id INTEGER,action TEXT,changes TEXT,operator_id INTEGER,operator_name TEXT,created_at TEXT DEFAULT CURRENT_TIMESTAMP',
|
|
|
] as $name => $columns) DB::statement('CREATE TABLE bot_' . $name . ' (' . $columns . ')');
|
|
] as $name => $columns) DB::statement('CREATE TABLE bot_' . $name . ' (' . $columns . ')');
|
|
|
(require dirname(__DIR__, 2) . '/database/migrations/2026_10_26_140000_create_reporting_tables.php')->up();
|
|
(require dirname(__DIR__, 2) . '/database/migrations/2026_10_26_140000_create_reporting_tables.php')->up();
|
|
|
|
|
+ Schema::table('reporting_orders', fn (Blueprint $table) => $table->text('third_game_commission')->nullable());
|
|
|
DB::table('reporting_settings')->where('id', 1)->update(['activity_started_at' => '2026-10-25 00:00:00']);
|
|
DB::table('reporting_settings')->where('id', 1)->update(['activity_started_at' => '2026-10-25 00:00:00']);
|
|
|
DB::table('share_policies')->insert(['effective_date' => '2026-10-01', 'valid_member_min_deposit' => '20', 'valid_member_min_effective_bet' => '100', 'rules' => json_encode(ShareDefaults::rules())]);
|
|
DB::table('share_policies')->insert(['effective_date' => '2026-10-01', 'valid_member_min_deposit' => '20', 'valid_member_min_effective_bet' => '100', 'rules' => json_encode(ShareDefaults::rules())]);
|
|
|
DB::table('share_system_state')->insert(['key' => 'initialized_at', 'value' => '2026-10-01 00:00:00']);
|
|
DB::table('share_system_state')->insert(['key' => 'initialized_at', 'value' => '2026-10-01 00:00:00']);
|
|
@@ -742,21 +744,22 @@ class ReportingTest extends TestCase
|
|
|
{
|
|
{
|
|
|
DB::statement('CREATE TABLE bot_third_game_orders (id INTEGER PRIMARY KEY,order_key TEXT,member_id TEXT,user_id INTEGER,
|
|
DB::statement('CREATE TABLE bot_third_game_orders (id INTEGER PRIMARY KEY,order_key TEXT,member_id TEXT,user_id INTEGER,
|
|
|
platform TEXT,player_id TEXT,game_type INTEGER,game_name TEXT,bet_amount TEXT,valid_amount TEXT,settled_amount TEXT,
|
|
platform TEXT,player_id TEXT,game_type INTEGER,game_name TEXT,bet_amount TEXT,valid_amount TEXT,settled_amount TEXT,
|
|
|
- status INTEGER,currency TEXT,bet_time TEXT,last_update_time TEXT,created_at TEXT,updated_at TEXT)');
|
|
|
|
|
- $this->app->instance(\App\Services\ThirdGameCommissionService::class, new class extends \App\Services\ThirdGameCommissionService {
|
|
|
|
|
- public function rates(): array { return ['date' => '2026-10-26', 'fetched_at' => '2026-10-26 08:00:00',
|
|
|
|
|
- 'available' => true, 'ratios' => ['pg' => '0.09', 'ag' => '0.20', 'free' => '0']]; }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ status INTEGER,currency TEXT,bet_time TEXT,last_update_time TEXT,created_at TEXT,updated_at TEXT,
|
|
|
|
|
+ third_game_commission TEXT)');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private function commissionOrder(int $id, array $extra = []): void
|
|
private function commissionOrder(int $id, array $extra = []): void
|
|
|
{
|
|
{
|
|
|
$user = $extra['user_id'] ?? 2;
|
|
$user = $extra['user_id'] ?? 2;
|
|
|
- DB::table('third_game_orders')->insert($extra + ['id' => $id, 'order_key' => hash('sha256', 'commission-' . $id),
|
|
|
|
|
|
|
+ $order = $extra + ['id' => $id, 'order_key' => hash('sha256', 'commission-' . $id),
|
|
|
'member_id' => (string) ($user * 100), 'user_id' => $user, 'platform' => 'pg', 'player_id' => 'u' . $user,
|
|
'member_id' => (string) ($user * 100), 'user_id' => $user, 'platform' => 'pg', 'player_id' => 'u' . $user,
|
|
|
'game_type' => 2, 'game_name' => 'game-' . $id, 'bet_amount' => '100', 'valid_amount' => '95',
|
|
'game_type' => 2, 'game_name' => 'game-' . $id, 'bet_amount' => '100', 'valid_amount' => '95',
|
|
|
'settled_amount' => '-100', 'status' => 1, 'currency' => 'CNY', 'bet_time' => '2026-10-25 23:00:00',
|
|
'settled_amount' => '-100', 'status' => 1, 'currency' => 'CNY', 'bet_time' => '2026-10-25 23:00:00',
|
|
|
- 'last_update_time' => '2026-10-26 10:00:00', 'created_at' => '2026-10-25 23:00:00', 'updated_at' => '2026-10-26 10:00:00']);
|
|
|
|
|
|
|
+ 'last_update_time' => '2026-10-26 10:00:00', 'created_at' => '2026-10-25 23:00:00', 'updated_at' => '2026-10-26 10:00:00'];
|
|
|
|
|
+ $ratio = ['pg' => '0.09', 'ag' => '0.20', 'free' => '0'][strtolower($order['platform'])] ?? null;
|
|
|
|
|
+ $amount = ThirdGameCommissionService::result($order['status'], $order['settled_amount']) === 'loss' && $ratio !== null
|
|
|
|
|
+ ? ThirdGameCommissionService::charge($order['settled_amount'], $ratio) : null;
|
|
|
|
|
+ DB::table('third_game_orders')->insert($order + ['third_game_commission' => $amount]);
|
|
|
$this->sync->publish('third_game_orders', $id);
|
|
$this->sync->publish('third_game_orders', $id);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -770,27 +773,39 @@ class ReportingTest extends TestCase
|
|
|
$this->commissionOrder(5, ['user_id' => 1, 'settled_amount' => '-10']);
|
|
$this->commissionOrder(5, ['user_id' => 1, 'settled_amount' => '-10']);
|
|
|
$this->bet();
|
|
$this->bet();
|
|
|
$business = $this->reader->summary($this->today());
|
|
$business = $this->reader->summary($this->today());
|
|
|
- $this->assertSame('20.3000000000', $business['summary']['third_game_commission']);
|
|
|
|
|
- $this->assertSame('159.7000000000', $business['summary']['company_profit']);
|
|
|
|
|
|
|
+ $this->assertSame('23.9000000000', $business['summary']['third_game_commission']);
|
|
|
|
|
+ $this->assertSame('156.1000000000', $business['summary']['company_profit']);
|
|
|
$categories = collect($business['categories'])->keyBy('group_id');
|
|
$categories = collect($business['categories'])->keyBy('group_id');
|
|
|
- $this->assertSame('10.3000000000', $categories['third:2']['third_game_commission']);
|
|
|
|
|
|
|
+ $this->assertSame('13.9000000000', $categories['third:2']['third_game_commission']);
|
|
|
|
|
+ $this->assertSame('76.1000000000', $categories['third:2']['game_net_profit']);
|
|
|
$this->assertSame('10.0000000000', $categories['third:1']['third_game_commission']);
|
|
$this->assertSame('10.0000000000', $categories['third:1']['third_game_commission']);
|
|
|
$this->assertSame('0.0000000000', $categories['self:lottery']['third_game_commission']);
|
|
$this->assertSame('0.0000000000', $categories['self:lottery']['third_game_commission']);
|
|
|
foreach (['promotion', 'sharing'] as $type) {
|
|
foreach (['promotion', 'sharing'] as $type) {
|
|
|
$report = $this->reader->summary($this->today(['report_type' => $type]));
|
|
$report = $this->reader->summary($this->today(['report_type' => $type]));
|
|
|
- $this->assertSame('19.4000000000', $report['summary']['third_game_commission']);
|
|
|
|
|
|
|
+ $this->assertSame('23.0000000000', $report['summary']['third_game_commission']);
|
|
|
$this->assertSame(9, count($report['categories']));
|
|
$this->assertSame(9, count($report['categories']));
|
|
|
}
|
|
}
|
|
|
- $this->assertSame('15.4000000000', $this->reader->details($this->today(['report_type' => 'promotion', 'group_id' => '10']))['data'][0]['third_game_commission']);
|
|
|
|
|
- $funds = $this->reader->funds(['start_date' => '2026-10-25', 'end_date' => '2026-10-26', 'limit' => 1]);
|
|
|
|
|
- $this->assertSame('20.3000000000', $funds['summary']['third_game_commission']);
|
|
|
|
|
- $this->assertSame('20.3000000000', $funds['data'][0]['third_game_commission']);
|
|
|
|
|
- $this->assertSame('0.0000000000', $this->reader->funds(['start_date' => '2026-10-25', 'end_date' => '2026-10-26', 'limit' => 1, 'page' => 2])['data'][0]['third_game_commission']);
|
|
|
|
|
|
|
+ $this->assertSame('19.0000000000', $this->reader->details($this->today(['report_type' => 'promotion', 'group_id' => '10']))['data'][0]['third_game_commission']);
|
|
|
|
|
+ $range = ['start_date' => '2026-10-25', 'end_date' => '2026-10-26', 'limit' => 1];
|
|
|
|
|
+ $funds = $this->reader->funds($range);
|
|
|
|
|
+ $this->assertSame('23.9000000000', $funds['summary']['third_game_commission']);
|
|
|
|
|
+ $this->assertSame('23.9000000000', $funds['data'][0]['third_game_commission']);
|
|
|
|
|
+ $this->assertCount(9, $funds['summary']['categories']);
|
|
|
|
|
+ $this->assertSame('76.1000000000', collect($funds['data'][0]['categories'])->keyBy('group_id')['third:2']['game_net_profit']);
|
|
|
|
|
+ $previous = $this->reader->funds($range + ['page' => 2]);
|
|
|
|
|
+ $this->assertSame($funds['summary'], $previous['summary']);
|
|
|
|
|
+ $this->assertSame('0.0000000000', collect($previous['data'][0]['categories'])->keyBy('group_id')['third:2']['third_game_commission']);
|
|
|
|
|
+ // Bet on 10-25, settled on 10-26: game result is counted on the settlement day only, never on both days.
|
|
|
|
|
+ $this->assertSame('0.0000000000', collect($previous['data'][0]['categories'])->keyBy('group_id')['third:2']['game_gross_profit']);
|
|
|
|
|
+ $this->assertSame('0.0000000000', $previous['data'][0]['game_gross_profit']);
|
|
|
|
|
+ $this->assertSame('90.0000000000', collect($funds['data'][0]['categories'])->keyBy('group_id')['third:2']['game_gross_profit']);
|
|
|
|
|
+ $this->assertSame($funds['summary']['game_gross_profit'], $funds['data'][0]['game_gross_profit']);
|
|
|
$finance = \App\Services\FundsSummaryService::summary('2026-10-26', '2026-10-26');
|
|
$finance = \App\Services\FundsSummaryService::summary('2026-10-26', '2026-10-26');
|
|
|
$expense = collect($finance['expense'])->keyBy('key');
|
|
$expense = collect($finance['expense'])->keyBy('key');
|
|
|
- $this->assertSame('20.30', $expense['third_game_commission']['amount']);
|
|
|
|
|
|
|
+ $this->assertSame('23.90', $expense['third_game_commission']['amount']);
|
|
|
$this->assertSame(3, $expense['third_game_commission']['people']);
|
|
$this->assertSame(3, $expense['third_game_commission']['people']);
|
|
|
- $this->assertSame('20.30', $finance['expense_total']);
|
|
|
|
|
|
|
+ $this->assertSame('23.90', $finance['expense_total']);
|
|
|
|
|
+ $this->assertSame('116.10', $finance['third_game_net_profit']);
|
|
|
$this->assertSame('0.00', $expense['third_party_fee']['amount']);
|
|
$this->assertSame('0.00', $expense['third_party_fee']['amount']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -806,11 +821,19 @@ class ReportingTest extends TestCase
|
|
|
$this->assertNull($row['third_game_commission']);
|
|
$this->assertNull($row['third_game_commission']);
|
|
|
$this->assertSame('9.0000000000', $row['known_third_game_commission']);
|
|
$this->assertSame('9.0000000000', $row['known_third_game_commission']);
|
|
|
$this->assertNull($row['company_profit']);
|
|
$this->assertNull($row['company_profit']);
|
|
|
|
|
+ $this->assertNull($row['game_net_profit']);
|
|
|
$this->assertSame('0.0000000000', $row['net_cash_flow']);
|
|
$this->assertSame('0.0000000000', $row['net_cash_flow']);
|
|
|
$this->assertSame(1, $row['quality']['missing_platform_ratio']);
|
|
$this->assertSame(1, $row['quality']['missing_platform_ratio']);
|
|
|
|
|
+ $funds = $this->reader->funds($this->today());
|
|
|
|
|
+ $category = collect($funds['data'][0]['categories'])->keyBy('group_id')['third:2'];
|
|
|
|
|
+ $this->assertNull($category['third_game_commission']);
|
|
|
|
|
+ $this->assertNull($category['game_net_profit']);
|
|
|
|
|
+ $this->assertSame('9.0000000000', $category['known_third_game_commission']);
|
|
|
|
|
+ $this->assertSame(1, $category['quality']['missing_platform_ratio']);
|
|
|
$finance = \App\Services\FundsSummaryService::summary();
|
|
$finance = \App\Services\FundsSummaryService::summary();
|
|
|
$this->assertNull(collect($finance['expense'])->keyBy('key')['third_game_commission']['amount']);
|
|
$this->assertNull(collect($finance['expense'])->keyBy('key')['third_game_commission']['amount']);
|
|
|
$this->assertNull($finance['expense_total']);
|
|
$this->assertNull($finance['expense_total']);
|
|
|
|
|
+ $this->assertNull($finance['third_game_net_profit']);
|
|
|
$this->assertSame('9.00', $finance['known_expense_total']);
|
|
$this->assertSame('9.00', $finance['known_expense_total']);
|
|
|
$this->assertSame('9.0000000000', $finance['third_game_commission_meta']['known_amount']);
|
|
$this->assertSame('9.0000000000', $finance['third_game_commission_meta']['known_amount']);
|
|
|
}
|
|
}
|
|
@@ -834,24 +857,47 @@ class ReportingTest extends TestCase
|
|
|
$this->assertSame('9.0000000000', $finance['third_game_commission_meta']['known_amount']);
|
|
$this->assertSame('9.0000000000', $finance['third_game_commission_meta']['known_amount']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function test_all_report_types_and_finance_share_one_daily_supplier_request(): void
|
|
|
|
|
|
|
+ public function test_report_and_finance_agree_on_verified_dates_skip_foreign_players_and_withhold_profit_on_sync_errors(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->commissionOrders();
|
|
|
|
|
+ $this->commissionOrder(1);
|
|
|
|
|
+ // An old snapshot of the other environment's player, never given a commission.
|
|
|
|
|
+ $this->commissionOrder(2, ['player_id' => 'tu2', 'platform' => 'unknown', 'settled_amount' => '-50']);
|
|
|
|
|
+ $summary = $this->reader->summary($this->today())['summary'];
|
|
|
|
|
+ $this->assertSame('9.0000000000', $summary['third_game_commission'], 'foreign snapshot is not in the report');
|
|
|
|
|
+ $this->assertSame('91.0000000000', $summary['game_net_profit']);
|
|
|
|
|
+ $this->assertSame('9.00', \App\Services\FundsSummaryService::summary('2026-10-26', '2026-10-26')['expense_total']);
|
|
|
|
|
+
|
|
|
|
|
+ // 结算日核对 moves the order to 10-25 in the report; finance must follow the same date.
|
|
|
|
|
+ $id = (int) DB::table('reporting_orders')->where('parent_source', 'third_game_orders')->where('parent_id', '1')->value('id');
|
|
|
|
|
+ $this->admin->reconcileOrder(['id' => $id, 'settled_at' => '2026-10-25 23:30:00', 'evidence' => '对账单']);
|
|
|
|
|
+ $this->assertSame('0.0000000000', $this->reader->summary($this->today())['summary']['third_game_commission']);
|
|
|
|
|
+ $this->assertSame('0.00', \App\Services\FundsSummaryService::summary('2026-10-26', '2026-10-26')['expense_total']);
|
|
|
|
|
+ $this->assertSame('9.00', \App\Services\FundsSummaryService::summary('2026-10-25', '2026-10-25')['expense_total']);
|
|
|
|
|
+
|
|
|
|
|
+ DB::table('reporting_errors')->insert(['source' => 'third_game_orders', 'source_id' => '1', 'reason' => 'sync failed',
|
|
|
|
|
+ 'created_at' => now(), 'updated_at' => now()]);
|
|
|
|
|
+ $range = ['start_date' => '2026-10-25', 'end_date' => '2026-10-26'];
|
|
|
|
|
+ $this->assertNull($this->reader->summary($range)['summary']['game_net_profit']);
|
|
|
|
|
+ $funds = $this->reader->funds($range);
|
|
|
|
|
+ $this->assertNull($funds['summary']['game_net_profit']);
|
|
|
|
|
+ $this->assertNull(collect($funds['data'][0]['categories'])->keyBy('group_id')['third:2']['game_net_profit']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_reports_and_finance_read_the_saved_commission_without_requesting_quota(): void
|
|
|
{
|
|
{
|
|
|
$this->commissionOrders(); $this->commissionOrder(1);
|
|
$this->commissionOrders(); $this->commissionOrder(1);
|
|
|
- $this->app->instance(\App\Services\ThirdGameCommissionService::class, new \App\Services\ThirdGameCommissionService());
|
|
|
|
|
- $this->app->instance('cache', new \Illuminate\Cache\Repository(new \Illuminate\Cache\ArrayStore()));
|
|
|
|
|
$this->app->instance(\Illuminate\Http\Client\Factory::class, new \Illuminate\Http\Client\Factory());
|
|
$this->app->instance(\Illuminate\Http\Client\Factory::class, new \Illuminate\Http\Client\Factory());
|
|
|
config(['third_game.api_url' => 'https://supplier.test', 'third_game.sn' => 'test', 'third_game.key' => 'test']);
|
|
config(['third_game.api_url' => 'https://supplier.test', 'third_game.sn' => 'test', 'third_game.key' => 'test']);
|
|
|
\Illuminate\Support\Facades\Http::preventStrayRequests();
|
|
\Illuminate\Support\Facades\Http::preventStrayRequests();
|
|
|
- \Illuminate\Support\Facades\Http::fake(['https://supplier.test/api/server/quota' => \Illuminate\Support\Facades\Http::response([
|
|
|
|
|
- 'code' => 10000, 'data' => ['ratios' => [['platfrom' => 'pg', 'ratio' => 0.09]]],
|
|
|
|
|
- ])]);
|
|
|
|
|
|
|
+ \Illuminate\Support\Facades\Http::fake([]);
|
|
|
foreach (['business', 'promotion', 'sharing'] as $type) {
|
|
foreach (['business', 'promotion', 'sharing'] as $type) {
|
|
|
$report = $this->reader->summary($this->today(['report_type' => $type]));
|
|
$report = $this->reader->summary($this->today(['report_type' => $type]));
|
|
|
$this->assertSame('9.0000000000', $report['summary']['third_game_commission']);
|
|
$this->assertSame('9.0000000000', $report['summary']['third_game_commission']);
|
|
|
}
|
|
}
|
|
|
$this->assertSame('9.0000000000', $this->reader->funds($this->today())['summary']['third_game_commission']);
|
|
$this->assertSame('9.0000000000', $this->reader->funds($this->today())['summary']['third_game_commission']);
|
|
|
$this->assertSame('9.00', \App\Services\FundsSummaryService::summary()['expense_total']);
|
|
$this->assertSame('9.00', \App\Services\FundsSummaryService::summary()['expense_total']);
|
|
|
- \Illuminate\Support\Facades\Http::assertSentCount(1);
|
|
|
|
|
|
|
+ \Illuminate\Support\Facades\Http::assertNothingSent();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function test_finance_commission_keeps_large_decimal_precision_and_reports_missing_source_data(): void
|
|
public function test_finance_commission_keeps_large_decimal_precision_and_reports_missing_source_data(): void
|