[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', 'upstream_mirrors.datasets' => [ MarketingAccountBillMirrorRepository::TAOBAO => $this->definition('tmall_spread_bills', 'js_tmall_spread_bills'), MarketingAccountBillMirrorRepository::PINDUODUO => $this->definition('pdd_spread_bills', 'js_pdd_spread_bills'), MarketingAccountBillMirrorRepository::ALIBABA => $this->definition('alibaba_bill', 'js_alibaba_bill'), MarketingAccountBillMirrorRepository::JD_RECHARGE => $this->definition('jd_spreadBills', 'js_jd_spread_bills'), MarketingAccountBillMirrorRepository::JD_CONSUMPTION => $this->definition('jd_costBills', 'js_jd_cost_bills'), ], ]); DB::purge('business_mirror'); $this->createTables(); $generic = new GenericBusinessMirrorRepository(app('db')); $this->mirror = new MarketingAccountBillMirrorRepository( new BusinessMirrorDatasetRegistry(), $generic, ); } public function test_ready_mirrors_drive_lists_summaries_options_monthly_balances_and_second_pages(): void { $db = DB::connection('business_mirror'); $db->table('js_tmall_spread_bills')->insert([ $this->taobao(1, '2026-05-31 23:00:00', 10000, 10000, 'DEBIT'), $this->taobao(2, '2026-06-01 09:00:00', 5000, 15000, 'DEBIT'), $this->taobao(3, '2026-06-02 09:00:00', 2000, 13000, 'CREDIT'), ]); $db->table('js_pdd_spread_bills')->insert([ $this->pinduoduo(1, '2026-05-31 23:00:00', 100, 100, 1), $this->pinduoduo(2, '2026-06-01 09:00:00', 50, 150, 1), $this->pinduoduo(3, '2026-06-02 09:00:00', 20, 130, 2), ]); $db->table('js_alibaba_bill')->insert([ ['id' => 1, 'shopName' => '阿里店', 'tradeDay' => '2026-06-01 09:00:00', 'tradeTypeName' => '全站', 'tradeAMTIn' => 10, 'tradeAMTOut' => 1], ['id' => 2, 'shopName' => '阿里店', 'tradeDay' => '2026-06-02 09:00:00', 'tradeTypeName' => '全站', 'tradeAMTIn' => 20, 'tradeAMTOut' => 2], ]); $db->table('js_jd_spread_bills')->insert([ ['id' => 1, 'shopName' => '京东店', 'amount' => 100, 'balanceAmount' => 100, 'orderTypeName' => '充值', 'pk' => 'R1', 'createDate' => '2026-06-01 10:00:00', 'updateTime' => '2026-06-01 10:01:00', 'statusName' => '成功', 'remark' => ''], ['id' => 2, 'shopName' => '京东店', 'amount' => 200, 'balanceAmount' => 300, 'orderTypeName' => '充值', 'pk' => 'R2', 'createDate' => '2026-06-02 10:00:00', 'updateTime' => '2026-06-02 10:01:00', 'statusName' => '成功', 'remark' => ''], ]); $db->table('js_jd_cost_bills')->insert([ ['id' => 1, 'shopName' => '京东店', 'amount' => -30, 'balanceAmount' => 270, 'orderTypeName' => '消耗', 'creatTime' => '2026-06-03 10:00:00', 'settleTime' => '2026-06-04 00:00:00', 'pk' => 'C1', 'invoiceApplyTypeName' => '推广费'], ]); $this->markReady(); $factory = $this->throwingFactory(); $taobao = new TmallTaobaoSpreadBillRepository($factory, $this->mirror); $pinduoduo = new PinduoduoSpreadBillRepository($factory, $this->mirror); $alibaba = new AlibabaBillRepository($factory, $this->mirror); $jd = new JdWalletBillRepository($factory, $this->mirror); $taobaoPage2 = $taobao->paginate('镜像店铺', '2026-06-01', '2026-06-30', null, 2, 1); $taobaoSummary = $taobao->summary('镜像店铺', '2026-06-01', '2026-06-30'); $taobaoMonthly = $taobao->paginateMonthlyBalances('镜像店铺', '2026-06', 1, 25); $pddPage2 = $pinduoduo->paginate('镜像店铺', '2026-06-01', '2026-06-30', null, 2, 1); $pddSummary = $pinduoduo->summary('镜像店铺', '2026-06-01', '2026-06-30'); $pddMonthly = $pinduoduo->paginateMonthlyBalances('镜像店铺', '2026-06', 1, 25); $alibabaPage2 = $alibaba->paginate('阿里店', '全站', '2026-06-01', '2026-06-30', 2, 1); $jdRechargePage2 = $jd->paginateRecharge('京东店', '2026-06-01', '2026-06-30', 2, 1); $taobaoExport = iterator_to_array($taobao->exportRows('镜像店铺', '2026-06-01', '2026-06-30', null, 1)); $alibabaExport = iterator_to_array($alibaba->exportRows('阿里店', '全站', '2026-06-01', '2026-06-30', 1)); $jdRechargeExport = iterator_to_array($jd->exportRows('recharge', '京东店', '2026-06-01', '2026-06-30', 1)); $jdConsumptionExport = iterator_to_array($jd->exportRows('consumption', '京东店', '2026-06-01', '2026-06-30', 1)); $this->assertSame(2, $taobaoPage2['total']); $this->assertSame(50.0, $taobaoPage2['rows'][0]['amount']); $this->assertSame(['镜像店铺'], $taobao->storeOptions()); $this->assertSame(30.0, $taobaoSummary['net']); $this->assertSame(100.0, $taobaoMonthly['rows'][0]['opening_balance']); $this->assertSame(130.0, $taobaoMonthly['rows'][0]['closing_balance']); $this->assertSame(0.0, $taobaoMonthly['rows'][0]['balance_difference']); $this->assertSame(2, $pddPage2['total']); $this->assertSame(50.0, $pddPage2['rows'][0]['amount']); $this->assertSame(30.0, $pddSummary['net']); $this->assertSame(100.0, $pddMonthly['rows'][0]['opening_balance']); $this->assertSame(130.0, $pddMonthly['rows'][0]['closing_balance']); $this->assertSame(0.0, $pddMonthly['rows'][0]['balance_difference']); $this->assertSame(2, $alibabaPage2['total']); $this->assertSame(10.0, $alibabaPage2['rows'][0]['income_amount']); $this->assertSame(['shops' => ['阿里店'], 'product_types' => ['全站']], $alibaba->filterOptions()); $this->assertSame(27.0, $alibaba->summary('阿里店', '全站')['net']); $this->assertSame(2, $jdRechargePage2['total']); $this->assertSame('R1', $jdRechargePage2['rows'][0]['order_number']); $this->assertSame(['京东店'], $jd->shopOptions()); $this->assertSame(300.0, $jd->summaryRecharge('京东店')['amount_total']); $this->assertSame(30.0, $jd->summaryConsumption('京东店')['amount_total']); $this->assertCount(2, $taobaoExport); $this->assertSame([20.0, 50.0], array_column($taobaoExport, 'amount')); $this->assertCount(2, $alibabaExport); $this->assertSame([20.0, 10.0], array_column($alibabaExport, 'income_amount')); $this->assertSame(['R2', 'R1'], array_column($jdRechargeExport, 'order_number')); $this->assertSame(['C1'], array_column($jdConsumptionExport, 'serial_number')); } public function test_unready_dataset_keeps_the_existing_upstream_repository_path(): void { $query = new class('jiansun') extends UpstreamReadOnlyQuery { public function scalar(string $sql, array $bindings = []): mixed { return 1; } public function select(string $sql, array $bindings = []): array { return [['shopName' => '上游回退', 'amount' => 8, 'balance' => 9, 'summary' => '', 'flowType' => 1, 'toAdvertiserFundType' => 1, 'createTime' => '2026-06-01']]; } }; $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, $this->mirror))->paginate(); $this->assertSame(1, $result['total']); $this->assertSame('上游回退', $result['rows'][0]['store_name']); } private function throwingFactory(): UpstreamReadOnlyQueryFactory { $query = new class('jiansun') extends UpstreamReadOnlyQuery { public function scalar(string $sql, array $bindings = []): mixed { throw new RuntimeException('upstream_should_not_be_called'); } public function select(string $sql, array $bindings = []): array { throw new RuntimeException('upstream_should_not_be_called'); } public function first(string $sql, array $bindings = []): ?array { throw new RuntimeException('upstream_should_not_be_called'); } }; return new class($query) extends UpstreamReadOnlyQueryFactory { public function __construct(private readonly UpstreamReadOnlyQuery $query) {} public function forSource(string $source): UpstreamReadOnlyQuery { return $this->query; } }; } private function createTables(): void { $schema = Schema::connection('business_mirror'); $schema->create('business_mirror_table_states', function (Blueprint $table): void { $table->string('dataset_key')->primary(); $table->dateTime('initial_completed_at')->nullable(); }); $schema->create('js_tmall_spread_bills', function (Blueprint $table): void { $table->integer('id'); $table->string('shopName')->nullable(); $table->dateTime('uniTransTime')->nullable(); $table->dateTime('transTime')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->decimal('balance', 18, 2)->nullable(); $table->string('comment')->nullable(); $table->string('finType')->nullable(); $table->string('bizType')->nullable(); $table->string('tradeType')->nullable(); }); $schema->create('js_pdd_spread_bills', function (Blueprint $table): void { $table->integer('id'); $table->string('shopName')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->decimal('balance', 18, 2)->nullable(); $table->string('summary')->nullable(); $table->integer('flowType')->nullable(); $table->integer('toAdvertiserFundType')->nullable(); $table->dateTime('createTime')->nullable(); }); $schema->create('js_alibaba_bill', function (Blueprint $table): void { $table->integer('id'); $table->string('shopName')->nullable(); $table->dateTime('tradeDay')->nullable(); $table->string('tradeTypeName')->nullable(); $table->decimal('tradeAMTIn', 18, 2)->nullable(); $table->decimal('tradeAMTOut', 18, 2)->nullable(); }); $schema->create('js_jd_spread_bills', function (Blueprint $table): void { $table->integer('id'); $table->string('shopName')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->decimal('balanceAmount', 18, 2)->nullable(); $table->string('orderTypeName')->nullable(); $table->string('pk')->nullable(); $table->dateTime('createDate')->nullable(); $table->dateTime('updateTime')->nullable(); $table->string('statusName')->nullable(); $table->string('remark')->nullable(); }); $schema->create('js_jd_cost_bills', function (Blueprint $table): void { $table->integer('id'); $table->string('shopName')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->decimal('balanceAmount', 18, 2)->nullable(); $table->string('orderTypeName')->nullable(); $table->dateTime('creatTime')->nullable(); $table->dateTime('settleTime')->nullable(); $table->string('pk')->nullable(); $table->string('invoiceApplyTypeName')->nullable(); }); } private function markReady(): void { foreach (array_keys((array) config('upstream_mirrors.datasets')) as $key) { DB::connection('business_mirror')->table('business_mirror_table_states')->insert([ 'dataset_key' => $key, 'initial_completed_at' => '2026-08-14 12:00:00', ]); } } private function definition(string $object, string $target): array { return ['source_key' => 'jiansun', 'schema' => 'dbo', 'object' => $object, 'target' => $target, 'keys' => ['id']]; } private function taobao(int $id, string $time, float $amount, float $balance, string $finType): array { return ['id' => $id, 'shopName' => '镜像店铺', 'uniTransTime' => $time, 'transTime' => $time, 'amount' => $amount, 'balance' => $balance, 'comment' => '', 'finType' => $finType, 'bizType' => '', 'tradeType' => '']; } private function pinduoduo(int $id, string $time, float $amount, float $balance, int $flowType): array { return ['id' => $id, 'shopName' => '镜像店铺', 'amount' => $amount, 'balance' => $balance, 'summary' => '', 'flowType' => $flowType, 'toAdvertiserFundType' => 1, 'createTime' => $time]; } }