*/ public array $calls = []; public function scalar(string $sql, array $bindings = []): mixed { $this->calls[] = compact('sql', 'bindings') + ['method' => 'scalar']; return 1; } public function select(string $sql, array $bindings = []): array { $this->calls[] = compact('sql', 'bindings') + ['method' => 'select']; return [[ 'shopName' => '测试店铺', 'amount' => 100, 'balance' => 200, 'summary' => '测试', 'flowType' => 1, 'toAdvertiserFundType' => 1, 'createTime' => '2026-07-23 12:00:00', ]]; } public function first(string $sql, array $bindings = []): ?array { $this->calls[] = compact('sql', 'bindings') + ['method' => 'first']; return [ 'income_total' => 0, 'expense_total' => 100, 'cnt' => 1, ]; } }; $factory = new class($query) extends UpstreamReadOnlyQueryFactory { public function __construct(private readonly UpstreamReadOnlyQuery $query) { } public function forSource(string $source): UpstreamReadOnlyQuery { TestCase::assertSame('jiansun', $source); return $this->query; } }; $repository = new PinduoduoSpreadBillRepository($factory); $page = $repository->paginate(null, null, null, 'income', 1, 25); $summary = $repository->summary(null, null, null, 'expense'); $this->assertSame(1, $page['total']); $this->assertSame('收入', $page['rows'][0]['income_expense_type']); $this->assertSame(-100.0, $summary['net']); $this->assertStringContainsString('flowType = 1', $query->calls[0]['sql']); $this->assertStringContainsString('flowType = 1', $query->calls[1]['sql']); $this->assertStringContainsString('flowType = 2', $query->calls[2]['sql']); } public function test_monthly_balances_follow_the_reconciliation_formula(): void { $query = new class('jiansun') extends UpstreamReadOnlyQuery { public array $calls = []; public function scalar(string $sql, array $bindings = []): mixed { $this->calls[] = compact('sql', 'bindings') + ['method' => 'scalar']; return 1; } public function select(string $sql, array $bindings = []): array { $this->calls[] = compact('sql', 'bindings') + ['method' => 'select']; return [[ 'shopName' => '拼多多_测试店铺', 'balance_date' => '2026-06-30', 'opening_balance' => 1000, 'income_total' => 500, 'expense_total' => 200, 'system_closing_balance' => 1300, ]]; } }; $factory = new class($query) extends UpstreamReadOnlyQueryFactory { public function __construct(private readonly UpstreamReadOnlyQuery $query) {} public function forSource(string $source): UpstreamReadOnlyQuery { return $this->query; } }; $result = (new PinduoduoSpreadBillRepository($factory))->paginateMonthlyBalances( '拼多多_测试店铺', '2026-06', 2, 25, ); $row = $result['rows'][0]; $this->assertSame(1300.0, $row['closing_balance']); $this->assertSame(0.0, $row['balance_difference']); $this->assertSame('已核对', $row['verification_status']); $this->assertStringContainsString('flowType = 1', $query->calls[1]['sql']); $this->assertStringContainsString('flowType = 2', $query->calls[1]['sql']); $this->assertStringContainsString('p.createTime < m.month_start', $query->calls[1]['sql']); $this->assertSame(['拼多多_测试店铺', '2026-06-01', '2026-06-01', 26, 50], $query->calls[1]['bindings']); } }