*/ 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']); } public function test_export_rows_reads_every_filtered_mirror_batch(): void { $mirror = new class extends MarketingAccountBillMirrorRepository { public array $calls = []; public function __construct() {} public function ready(string $datasetKey): bool { TestCase::assertSame(MarketingAccountBillMirrorRepository::PINDUODUO, $datasetKey); return true; } public function paginatePinduoduo( ?string $storeName, ?string $fromDate, ?string $toDate, ?string $flowType, int $page, int $pageSize, ): array { $this->calls[] = compact('storeName', 'fromDate', 'toDate', 'flowType', 'page', 'pageSize'); $row = static fn (int $id): array => [ 'id' => $id, 'shopName' => '筛选店铺', 'amount' => $id * 10, 'balance' => 100, 'summary' => '测试导出', 'flowType' => 1, 'toAdvertiserFundType' => 1, 'createTime' => '2026-07-23 12:00:00', ]; return $page === 1 ? ['rows' => [$row(1), $row(2)], 'total' => 3] : ['rows' => [$row(3)], 'total' => 3]; } }; $query = new UpstreamReadOnlyQuery('jiansun'); $factory = new class($query) extends UpstreamReadOnlyQueryFactory { public function __construct(private readonly UpstreamReadOnlyQuery $query) {} public function forSource(string $source): UpstreamReadOnlyQuery { return $this->query; } }; $repository = new PinduoduoSpreadBillRepository($factory, $mirror); $rows = iterator_to_array($repository->exportRows( '筛选店铺', '2026-07-01', '2026-07-31', 'income', 2, )); $this->assertCount(3, $rows); $this->assertSame([10.0, 20.0, 30.0], array_column($rows, 'amount')); $this->assertSame([1, 2], array_column($mirror->calls, 'page')); $this->assertSame(['筛选店铺', '筛选店铺'], array_column($mirror->calls, 'storeName')); $this->assertSame(['income', 'income'], array_column($mirror->calls, 'flowType')); } }