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_raw' => 100000, 'income_total_raw' => 50000, 'expense_total_raw' => 20000, 'system_closing_balance_raw' => 130000, ]]; } }; $factory = new class($query) extends UpstreamReadOnlyQueryFactory { public function __construct(private readonly UpstreamReadOnlyQuery $query) {} public function forSource(string $source): UpstreamReadOnlyQuery { return $this->query; } }; $result = (new TmallTaobaoSpreadBillRepository($factory))->paginateMonthlyBalances( '淘系_测试店铺', '2026-06', 2, 25, ); $row = $result['rows'][0]; $this->assertSame(1000.0, $row['opening_balance']); $this->assertSame(500.0, $row['income_total']); $this->assertSame(200.0, $row['expense_total']); $this->assertSame(1300.0, $row['closing_balance']); $this->assertSame(0.0, $row['balance_difference']); $this->assertSame('已核对', $row['verification_status']); $this->assertStringContainsString("finType = 'DEBIT'", $query->calls[1]['sql']); $this->assertStringContainsString("finType = 'CREDIT'", $query->calls[1]['sql']); $this->assertSame(['淘系_测试店铺', '2026-06-01', '2026-06-01', 26, 50], $query->calls[1]['bindings']); } public function test_filters_and_cent_amounts_are_normalized_consistently(): void { $query = new class('jiansun') extends UpstreamReadOnlyQuery { /** @var array */ 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' => '测试店铺', 'uniTransTime' => '2026-07-23 12:00:00', 'transTime' => '2026-07-23', 'amount' => 15050, 'balance' => 990050, 'comment' => '测试流水', 'finType' => 'DEBIT', 'bizType' => 'INTEL_CHARGE_POST', 'tradeType' => '', ]]; } public function first(string $sql, array $bindings = []): ?array { $this->calls[] = compact('sql', 'bindings') + ['method' => 'first']; return [ 'income_total_raw' => 15050, 'expense_total_raw' => 0, '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 TmallTaobaoSpreadBillRepository($factory); $page = $repository->paginate('测试店铺', '2026-07-01', '2026-07-23', 'income', 1, 25); $summary = $repository->summary('测试店铺', '2026-07-01', '2026-07-23', 'income'); $this->assertSame(1, $page['total']); $this->assertSame(150.5, $page['rows'][0]['amount']); $this->assertSame(9900.5, $page['rows'][0]['balance']); $this->assertSame('收入', $page['rows'][0]['income_expense_type']); $this->assertSame('充值', $page['rows'][0]['transaction_type']); $this->assertSame(150.5, $summary['income_total']); $this->assertSame(150.5, $summary['net']); $this->assertStringContainsString('finType = \'DEBIT\'', $query->calls[0]['sql']); $this->assertSame( ['测试店铺', '2026-07-01', '2026-07-23'], $query->calls[0]['bindings'] ); $this->assertStringContainsString('finType = \'DEBIT\'', $query->calls[1]['sql']); $this->assertStringContainsString('finType = \'DEBIT\'', $query->calls[2]['sql']); } }