|
|
@@ -117,6 +117,114 @@ class ReportingTest extends TestCase
|
|
|
$this->assertSame('100.0000000000', $yesterday['bet_amount']); $this->assertSame('0.0000000000', $yesterday['payout_amount']);
|
|
|
}
|
|
|
|
|
|
+ private function f1LotteryWithoutCompletionContext(): void
|
|
|
+ {
|
|
|
+ DB::statement('CREATE TABLE bot_lhc_order (id INTEGER PRIMARY KEY,member_id TEXT,ordernum TEXT,amount TEXT,total_amount TEXT,win_amount TEXT,type INTEGER,lottery_status INTEGER,is_faker INTEGER DEFAULT 0,created_at INTEGER,updated_at INTEGER)');
|
|
|
+ DB::statement('CREATE TABLE bot_lhc_order_item (id INTEGER PRIMARY KEY,member_id TEXT,ordernum TEXT,lottery_status INTEGER,win_amount TEXT,updated_at INTEGER)');
|
|
|
+ $betAt = Carbon::parse('2026-10-26 09:00:00', 'Asia/Shanghai')->timestamp;
|
|
|
+ DB::table('lhc_order')->insert(['id' => 1, 'member_id' => '200', 'ordernum' => 'F1-live', 'amount' => '50', 'total_amount' => '100',
|
|
|
+ 'win_amount' => '190', 'type' => 6, 'lottery_status' => 2, 'created_at' => $betAt, 'updated_at' => $betAt + 3600]);
|
|
|
+ DB::table('lhc_order_item')->insert([
|
|
|
+ ['id' => 1, 'member_id' => '200', 'ordernum' => 'F1-live', 'lottery_status' => 2, 'win_amount' => '190', 'updated_at' => $betAt + 3600],
|
|
|
+ ['id' => 2, 'member_id' => '200', 'ordernum' => 'F1-live', 'lottery_status' => 1, 'win_amount' => '0', 'updated_at' => $betAt + 3600],
|
|
|
+ ]);
|
|
|
+ $this->context('lhc_order', 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function f1PayoutReceipt(array $extra = []): void
|
|
|
+ {
|
|
|
+ DB::table('balance_logs')->insert($extra + ['member_id' => '200', 'type' => 1, 'related_id' => 1, 'amount' => '190',
|
|
|
+ 'before_balance' => '100', 'after_balance' => '290', 'change_type' => '极速28中奖', 'created_at' => '2026-10-26 10:00:00']);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_f1_wallet_receipt_restores_funds_business_category_and_sharing_settlement_totals(): void
|
|
|
+ {
|
|
|
+ $this->f1LotteryWithoutCompletionContext(); $this->f1PayoutReceipt();
|
|
|
+ $before = DB::table('lhc_order')->get()->toJson();
|
|
|
+ $this->sync->publish('lhc_order', 1); $this->sync->publish('lhc_order', 1);
|
|
|
+ $funds = $this->reader->funds($this->today());
|
|
|
+ $business = $this->reader->summary($this->today());
|
|
|
+ $category = collect($business['categories'])->keyBy('group_id')['self:lottery'];
|
|
|
+ $sharing = $this->reader->summary($this->today(['report_type' => 'sharing']));
|
|
|
+ foreach ([$funds['summary'], $funds['data'][0], $business['summary'], $category, $sharing['summary']] as $row) {
|
|
|
+ $this->assertSame('100.0000000000', $row['bet_amount']);
|
|
|
+ $this->assertSame('100.0000000000', $row['settled_bet_amount']);
|
|
|
+ $this->assertSame('190.0000000000', $row['payout_amount']);
|
|
|
+ $this->assertSame('-90.0000000000', $row['game_gross_profit']);
|
|
|
+ }
|
|
|
+ $this->assertSame('F1彩票', $category['label']);
|
|
|
+ $groups = $this->reader->details($this->today(['report_type' => 'sharing']))['data'];
|
|
|
+ $this->assertSame('1', $groups[0]['group_id']);
|
|
|
+ $this->assertSame('190.0000000000', $groups[0]['payout_amount']);
|
|
|
+ $this->assertSame(2, DB::table('reporting_orders')->count());
|
|
|
+ $this->assertSame('2026-10-26 10:00:00', DB::table('reporting_orders')->value('settled_at'));
|
|
|
+ $this->assertSame($before, DB::table('lhc_order')->get()->toJson());
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_bulk_settlement_capture_preserves_event_attribution_and_records_actual_transition_once(): void
|
|
|
+ {
|
|
|
+ $this->f1LotteryWithoutCompletionContext();
|
|
|
+ $this->sync->publish('lhc_order', 1);
|
|
|
+ $this->assertSame('0.0000000000', $this->reader->funds($this->today())['summary']['payout_amount']);
|
|
|
+ $before = DB::table('lhc_order')->get()->toJson();
|
|
|
+ DB::table('users')->where('id', 2)->update(['agent_id' => 20]);
|
|
|
+ \App\Services\Reporting\ReportContext::lotterySettled(1);
|
|
|
+ Carbon::setTestNow(Carbon::parse('2026-10-26 16:10:00', 'Asia/Shanghai'));
|
|
|
+ \App\Services\Reporting\ReportContext::lotterySettled(1);
|
|
|
+ $context = DB::table('reporting_contexts')->where('source', 'lhc_order')->first();
|
|
|
+ $this->assertSame('2026-10-26 16:00:00', $context->succeeded_at);
|
|
|
+ $this->assertSame(10, (int) $context->agent_id); $this->assertSame(1, (int) $context->inviter_id);
|
|
|
+ $this->assertSame('H5', $context->client_platform);
|
|
|
+ $this->sync->publish('lhc_order', 1);
|
|
|
+ $row = $this->reader->funds($this->today())['summary'];
|
|
|
+ $this->assertSame('100.0000000000', $row['settled_bet_amount']);
|
|
|
+ $this->assertSame('190.0000000000', $row['payout_amount']);
|
|
|
+ $this->assertSame('-90.0000000000', $row['game_gross_profit']);
|
|
|
+ $this->assertSame($before, DB::table('lhc_order')->get()->toJson());
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function invalidF1Receipts(): array
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ [['related_id' => 999]], [['member_id' => '100']], [['type' => 2]],
|
|
|
+ [['amount' => '191', 'after_balance' => '291']], [['after_balance' => '999']],
|
|
|
+ [['created_at' => '2026-10-25 10:00:00']], [['change_type' => '充值']],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @dataProvider invalidF1Receipts */
|
|
|
+ public function test_unrelated_or_unbalanced_receipts_do_not_fabricate_lottery_settlement_time(array $receipt): void
|
|
|
+ {
|
|
|
+ $this->f1LotteryWithoutCompletionContext(); $this->f1PayoutReceipt($receipt);
|
|
|
+ $this->sync->publish('lhc_order', 1);
|
|
|
+ $this->assertNull(DB::table('reporting_orders')->value('settled_at'));
|
|
|
+ $row = $this->reader->funds($this->today())['summary'];
|
|
|
+ $this->assertSame('100.0000000000', $row['bet_amount']);
|
|
|
+ $this->assertSame('0.0000000000', $row['payout_amount']);
|
|
|
+ $this->assertGreaterThan(0, $row['quality']['missing_time']);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_verified_lottery_time_takes_priority_over_payout_receipt_time(): void
|
|
|
+ {
|
|
|
+ $this->f1LotteryWithoutCompletionContext(); $this->f1PayoutReceipt();
|
|
|
+ DB::table('reporting_contexts')->where('source', 'lhc_order')->update(['settlement_verified' => 1, 'succeeded_at' => '2026-10-26 11:30:00']);
|
|
|
+ $this->sync->publish('lhc_order', 1);
|
|
|
+ $this->assertSame('2026-10-26 11:30:00', DB::table('reporting_orders')->value('settled_at'));
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_bulk_lottery_loss_is_counted_as_settled_gross_profit_without_a_prize_receipt(): void
|
|
|
+ {
|
|
|
+ $this->f1LotteryWithoutCompletionContext();
|
|
|
+ DB::table('lhc_order')->where('id', 1)->update(['lottery_status' => 1, 'win_amount' => '0']);
|
|
|
+ DB::table('lhc_order_item')->update(['lottery_status' => 1, 'win_amount' => '0']);
|
|
|
+ \App\Services\Reporting\ReportContext::lotterySettled(1);
|
|
|
+ $this->sync->publish('lhc_order', 1);
|
|
|
+ $row = $this->reader->funds($this->today())['summary'];
|
|
|
+ $this->assertSame('100.0000000000', $row['settled_bet_amount']);
|
|
|
+ $this->assertSame('0.0000000000', $row['payout_amount']);
|
|
|
+ $this->assertSame('100.0000000000', $row['game_gross_profit']);
|
|
|
+ }
|
|
|
+
|
|
|
public function test_missing_fees_and_success_time_never_produce_fabricated_net_values(): void
|
|
|
{
|
|
|
$event = $this->deposit(); $this->bet();
|
|
|
@@ -425,7 +533,9 @@ class ReportingTest extends TestCase
|
|
|
$this->assertSame('28.0000000000', $row['company_profit']);
|
|
|
foreach ([false, true] as $ranking) {
|
|
|
$details = $this->reader->details($this->today(), $ranking);
|
|
|
- $this->assertArrayNotHasKey('unallocated', $details);
|
|
|
+ // Agent-only costs belong to the separate metadata bucket, not a synthetic member row.
|
|
|
+ $this->assertSame('7.0000000000', $details['unallocated']['agent_commission']);
|
|
|
+ $this->assertSame('9.0000000000', $details['unallocated']['agent_accrual']);
|
|
|
$this->assertNotContains('unallocated', array_column($details['data'], 'group_id'));
|
|
|
$this->assertSame(2, $details['total']);
|
|
|
}
|