[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', 'upstream_mirrors.datasets' => [ StoreReceivingAlipayBillMirrorRepository::DATASET => [ 'source_key' => 'operations_replica', 'schema' => 'rpt', 'object' => 'jst_originalBillInfo', 'target' => 'ops_jst_original_bill_info', 'keys' => ['Id'], ], StoreReceivingAlipayBillMirrorRepository::SHOP_DATASET => [ 'source_key' => 'operations_replica', 'schema' => 'rpt', 'object' => 'jst_shopInfo', 'target' => 'ops_jst_shop_info', 'keys' => ['Id'], ], ], ]); DB::purge('business_mirror'); $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(); $table->string('status')->nullable(); $table->dateTime('last_succeeded_at')->nullable(); $table->unsignedBigInteger('mirror_row_count')->nullable(); $table->boolean('mirror_row_count_is_exact')->default(false); }); $schema->create('ops_jst_original_bill_info', function (Blueprint $table): void { $table->integer('Id'); $table->integer('shop_id')->nullable(); foreach (['channel', 'primary_key', 'fee_name', 'inout_type', 'p_auto_id', 'shop_i_id', 'shop_sku_id', 'sku_id', 'so_id', 'combine_type'] as $column) { $table->string($column)->nullable(); } $table->dateTime('fee_time')->nullable(); $table->dateTime('createTime')->nullable(); $table->dateTime('updateTime')->nullable(); $table->bigInteger('auto_id')->nullable(); $table->decimal('amount', 20, 6)->nullable(); }); $schema->create('ops_jst_shop_info', function (Blueprint $table): void { $table->integer('Id'); $table->integer('shop_id')->nullable(); $table->string('shop_name')->nullable(); $table->string('shop_site')->nullable(); }); $this->repository = new StoreReceivingAlipayBillMirrorRepository( new BusinessMirrorDatasetRegistry(), new GenericBusinessMirrorRepository(app('db')), ); } public function test_it_reads_operations_replica_rows_and_maps_shop_and_flow_fields(): void { $this->markReady(); DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([ ['Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店'], ['Id' => 11, 'shop_id' => 8, 'shop_name' => '淘宝-其他店'], ]); DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25), $this->row(2, 7, '2026-08-02 09:00:00', '-1', 20.10), $this->row(3, 8, '2026-08-03 09:00:00', '1', 88), ]); $result = $this->repository->paginate( ['store_name' => '天猫-经营店', 'start' => '2026-08-01', 'end' => '2026-08-31'], 'occurred_at', 'desc', 1, 10, ); $this->assertSame(2, $result['total']); $this->assertSame('P-2', $result['data'][0]['record_code']); $this->assertSame('天猫-经营店', $result['data'][0]['store_name']); $this->assertNull($result['data'][0]['business_type']); $this->assertNull($result['data'][0]['alipay_account']); $this->assertNull($result['data'][0]['balance']); $this->assertSame(100.25, $result['summary']['income']); $this->assertSame(20.10, $result['summary']['expense']); $this->assertSame(80.15, $result['summary']['balance']); $this->assertSame(2, $result['summary']['count']); } public function test_it_rejects_an_unready_mirror_instead_of_querying_upstream_from_http(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('store_receiving_alipay_bill_mirror_not_ready'); $this->repository->paginate([], 'occurred_at', 'desc', 1, 10); } public function test_imported_attachment_rows_enrich_matching_mirror_rows_for_the_taobao_page(): void { $this->markReady(); DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([ 'Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店', 'shop_site' => '淘宝天猫', ]); DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert( $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25), ); DB::table('ec_alipay_bills')->insert([ 'company_id' => 1, 'record_code' => 'P-1', 'store_name' => '天猫-附件店铺', 'custom_remark' => '模板数据', 'bill_item' => '交易收款', 'alipay_account' => '20880001', 'account_order_no' => 'ACCOUNT-1', 'business_serial_no' => 'SERIAL-1', 'merchant_order_no' => 'MERCHANT-1', 'business_base_order_no' => 'BASE-1', 'goods_name' => '附件商品', 'counterparty_account' => '买家', 'occurred_at' => '2026-08-01 09:00:00', 'income' => 100.25, 'expense' => 0, 'balance' => 300.50, 'business_type' => '交易付款', 'remark' => '备注', 'online_order_no' => 'ONLINE-1', 'online_real_store' => '天猫-附件店铺', 'business_description' => '业务描述', 'business_bill_source' => '支付宝', 'business_order_no' => 'BUSINESS-1', 'modifier' => '导入人', 'platform' => 'taobao', 'created_from' => 'attachment-import', 'created_at' => '2026-08-02 09:00:00', 'updated_at' => '2026-08-02 10:00:00', ]); $result = $this->repository->paginate([], 'occurred_at', 'desc', 1, 10); $this->assertSame(1, $result['total']); $this->assertSame('P-1', $result['data'][0]['record_code']); $this->assertSame('天猫-经营店', $result['data'][0]['store_name']); $this->assertSame('20880001', $result['data'][0]['alipay_account']); $this->assertSame(300.50, $result['data'][0]['balance']); $this->assertSame(100.25, $result['summary']['income']); $this->assertSame('succeeded', $result['sync']['status']); } public function test_export_uses_the_same_filters_and_mapping_as_the_page(): void { $this->markReady(); DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([ 'Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店', ]); DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25), $this->row(2, 7, '2026-08-02 09:00:00', '-1', 20.10), ]); $rows = iterator_to_array($this->repository->exportRows([ 'business_type' => '支出', 'source_channel' => 'alipayCSV', 'keyword' => 'SKU-2', ])); $this->assertCount(1, $rows); $this->assertSame('P-2', $rows[0]['record_code']); $this->assertSame(20.10, $rows[0]['expense']); } public function test_taobao_page_and_export_exclude_other_platform_shops(): void { $this->markReady(); $db = DB::connection('business_mirror'); $sites = [ 1 => '淘宝天猫', 2 => '拼多多', 3 => '京东商城', 4 => '头条放心购', 5 => '得物', 6 => '唯品会MP', 7 => '小红书', ]; foreach ($sites as $shopId => $site) { $db->table('ops_jst_shop_info')->insert([ 'Id' => $shopId, 'shop_id' => $shopId, 'shop_name' => $site . '店铺', 'shop_site' => $site, ]); $db->table('ops_jst_original_bill_info')->insert( $this->row($shopId, $shopId, '2026-08-01 09:00:00', '1', 10), ); } $result = $this->repository->paginate([], 'occurred_at', 'asc', 1, 20); $this->assertSame(2, $result['total']); $this->assertSame(['淘宝天猫店铺', '小红书店铺'], array_column($result['data'], 'store_name')); $this->assertSame(20.0, $result['summary']['income']); $this->assertSame(2, $result['summary']['count']); $this->assertSame( ['淘宝天猫店铺', '小红书店铺'], array_column(iterator_to_array($this->repository->exportRows([], 'occurred_at', 'asc')), 'store_name'), ); } public function test_channel_pages_keep_their_own_platform_rows(): void { $this->markReady(); $db = DB::connection('business_mirror'); $db->table('ops_jst_shop_info')->insert([ 'Id' => 1, 'shop_id' => 1, 'shop_name' => '拼多多店铺', 'shop_site' => '拼多多', ]); $db->table('ops_jst_original_bill_info')->insert( $this->row(1, 1, '2026-08-01 09:00:00', '1', 10, 'PinduoduoImport'), ); $result = $this->repository->paginateChannel( [], 'occurred_at', 'asc', 1, 20, 'PinduoduoImport', 'pinduoduo', ); $this->assertSame(1, $result['total']); $this->assertSame('拼多多店铺', $result['data'][0]['store_name']); } public function test_export_batches_preserve_sort_ties_nulls_and_channel_filter(): void { $this->markReady(); $db = DB::connection('business_mirror'); $table = $db->table('ops_jst_original_bill_info'); $fixtures = []; for ($id = 1; $id <= 2105; $id++) { $row = $this->row($id, 7, '2026-08-01 09:00:00', '1', 10); $row['fee_time'] = $id <= 1005 ? null : '2026-08-01 09:00:00'; $row['amount'] = $id <= 1005 ? null : 10; $fixtures[] = $row; } foreach (array_chunk($fixtures, 100) as $chunk) $table->insert($chunk); $table->insert($this->row(9999, 7, '2026-08-01 09:00:00', '1', 10, 'OtherChannel')); foreach (['occurred_at' => 'fee_time', 'income' => 'amount', 'record_code' => 'p_auto_id'] as $sort => $column) { foreach (['asc', 'desc'] as $direction) { $expected = (clone $table)->where('channel', 'CommonBillTGCPay') ->orderBy($column, $direction)->orderBy('Id', $direction)->pluck('Id')->map('strval')->all(); $db->flushQueryLog(); $db->enableQueryLog(); $rows = iterator_to_array($this->repository->exportChannelRows([], $sort, $direction, 'CommonBillTGCPay')); $this->assertSame($expected, array_column($rows, 'record_code')); $reads = array_filter($db->getQueryLog(), fn ($query) => str_contains($query['query'], 'select "b".*')); $this->assertNotEmpty($reads); foreach ($reads as $read) $this->assertStringContainsString('limit 1000', $read['query']); $db->disableQueryLog(); } } } public function test_export_does_not_chase_new_rows_inserted_after_it_starts(): void { $this->markReady(); $table = DB::connection('business_mirror')->table('ops_jst_original_bill_info'); for ($id = 1; $id <= 1001; $id++) { $table->insert($this->row($id, 7, '2026-08-01 09:00:00', '1', 10)); } $ids = []; foreach ($this->repository->exportRows([], 'record_code', 'asc') as $row) { $ids[] = $row['record_code']; if (count($ids) === 1) $table->insert($this->row(9999, 7, '2026-08-01 09:00:00', '1', 10)); } $this->assertCount(1001, $ids); $this->assertNotContains('9999', $ids); } public function test_channel_index_migration_and_rollback_preserve_rows(): void { config(['system_database.mode' => 'connected']); $table = DB::connection('business_mirror')->table('ops_jst_original_bill_info'); $table->insert($this->row(1, 7, '2026-08-01 09:00:00', '1', 10, str_repeat('A', 101))); $migration = require base_path('database/migrations/2026_09_09_170000_index_original_bill_channel_queries.php'); $before = (array) $table->first(); $migration->up(); $migration->up(); $this->assertTrue(Schema::connection('business_mirror')->hasIndex('ops_jst_original_bill_info', 'idx_original_bill_channel_time')); $this->assertEquals($before, (array) $table->first()); $migration->down(); $this->assertFalse(Schema::connection('business_mirror')->hasIndex('ops_jst_original_bill_info', 'idx_original_bill_channel_time')); $this->assertEquals($before, (array) $table->first()); } public function test_shop_filter_index_migration_and_rollback_preserve_rows(): void { config(['system_database.mode' => 'connected']); $table = DB::connection('business_mirror')->table('ops_jst_original_bill_info'); $table->insert($this->row(1, 7, '2026-08-01 09:00:00', '1', 10)); $before = (array) $table->first(); $migration = require base_path('database/migrations/2026_09_09_190000_index_account_bill_shop_filters.php'); $migration->up(); $migration->up(); $this->assertTrue(Schema::connection('business_mirror')->hasIndex('ops_jst_original_bill_info', 'idx_original_bill_channel_shop')); $this->assertEquals($before, (array) $table->first()); $migration->down(); $this->assertFalse(Schema::connection('business_mirror')->hasIndex('ops_jst_original_bill_info', 'idx_original_bill_channel_shop')); $this->assertEquals($before, (array) $table->first()); } public function test_taobao_scope_does_not_use_the_cross_platform_daily_summary(): void { $this->markReady(); DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([ 'Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店', ]); DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert( $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25), ); Schema::connection('business_mirror')->create('store_report_import_states', function (Blueprint $table): void { $table->string('dataset_key')->primary(); $table->string('active_generation')->nullable(); $table->dateTime('initial_completed_at')->nullable(); }); Schema::connection('business_mirror')->create('store_report_daily_rows', function (Blueprint $table): void { $table->string('generation'); $table->string('store_name'); $table->date('occurred_on'); $table->decimal('income', 20, 4); $table->decimal('expense', 20, 4); }); DB::connection('business_mirror')->table('store_report_import_states')->insert([ 'dataset_key' => 'store-report.daily-sales', 'active_generation' => 'daily-v1', 'initial_completed_at' => '2026-09-01 09:00:00', ]); DB::connection('business_mirror')->table('store_report_daily_rows')->insert([ 'generation' => 'daily-v1', 'store_name' => '天猫-经营店', 'occurred_on' => '2026-08-01', 'income' => 100.25, 'expense' => 20.10, ]); $result = $this->repository->paginate([ 'store_name' => '天猫-经营店', 'start' => '2026-08-01', 'end' => '2026-08-01', ], 'occurred_at', 'desc', 1, 10); $this->assertSame(['income' => 100.25, 'expense' => 0.0, 'balance' => 100.25, 'count' => 1], $result['summary']); } public function test_taobao_scoped_pages_share_one_aggregate_across_sync_state_churn(): void { $this->markReady(); $db = DB::connection('business_mirror'); $db->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 10), $this->row(2, 7, '2026-08-02 09:00:00', '-1', 5), ]); $state = $db->table('business_mirror_table_states')->where('dataset_key', StoreReceivingAlipayBillMirrorRepository::DATASET); $state->update(['mirror_row_count' => 2]); $db->enableQueryLog(); $first = $this->repository->paginate([], 'occurred_at', 'desc', 1, 1); $second = $this->repository->paginate(['store_name' => '', 'start' => null], 'occurred_at', 'desc', 2, 1); $this->assertSame(2, $first['total']); $this->assertSame(2, $second['total']); $this->assertNotSame($first['data'][0]['id'], $second['data'][0]['id']); $aggregateQueries = array_values(array_filter( $db->getQueryLog(), static fn (array $query): bool => str_contains(strtolower($query['query']), 'count(*)'), )); $this->assertCount(1, $aggregateQueries); foreach ($aggregateQueries as $query) { $this->assertStringContainsString('sum(case', strtolower($query['query'])); } $db->disableQueryLog(); $db->table('ops_jst_original_bill_info')->insert($this->row(3, 7, '2026-08-03 09:00:00', '1', 4)); $state->update(['mirror_row_count' => 3, 'last_succeeded_at' => '2026-09-08 09:00:00']); $this->assertSame(2, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); $this->assertSame(1, $this->repository->paginate(['business_type' => '支出'], 'occurred_at', 'desc', 1, 1)['total']); Cache::flush(); $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); $state->update(['status' => 'running', 'mirror_row_count' => 2]); $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); $state->update(['status' => 'failed']); $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); $state->update(['status' => 'running', 'mirror_row_count' => 3, 'mirror_row_count_is_exact' => true]); $db->flushQueryLog(); $db->enableQueryLog(); $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); $this->assertCount(0, array_filter($db->getQueryLog(), fn ($q) => str_contains(strtolower($q['query']), 'count(*)'))); $db->disableQueryLog(); $state->update(['status' => 'succeeded', 'mirror_row_count' => null]); $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); } public function test_douyin_reads_only_toutiao_fangxin_gou_rows_and_maps_flow_fields(): void { $this->markReady(); $db = DB::connection('business_mirror'); $db->table('ops_jst_shop_info')->insert([ 'Id' => 10, 'shop_id' => 7, 'shop_name' => '放心购旗舰店', ]); $db->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25, 'TouTiaoFXGZJ'), $this->row(2, 7, '2026-08-02 09:00:00', '-1', 20.10, 'alipayCSV'), $this->row(3, 7, '2026-08-02 10:00:00', '1', 55.40, 'TouTiaoFXGZJ'), ]); $douyin = new StoreReceivingDouyinBillMirrorRepository($this->repository); $result = $douyin->paginate([], 'occurred_at', 'desc', 1, 10); $this->assertSame(2, $result['total']); $this->assertSame('3', $result['data'][0]['record_code']); $this->assertSame('放心购旗舰店', $result['data'][0]['store_name']); $this->assertSame('TouTiaoFXGZJ', $result['data'][0]['payload']['settle_account']); $this->assertSame('头条放心购', $result['data'][0]['payload']['move_scene']); $this->assertSame(155.65, $result['summary']['income']); $this->assertSame(0.0, $result['summary']['expense']); $filtered = $douyin->paginate([ 'store_name' => '放心购', 'bill_item' => '交易佣', 'start' => '2026-08-01', 'end' => '2026-08-01', ], 'occurred_at', 'desc', 1, 10); $this->assertSame(1, $filtered['total']); $this->assertSame('1', $filtered['data'][0]['record_code']); $rows = iterator_to_array($douyin->exportRows([], 'occurred_at', 'asc')); $this->assertCount(2, $rows); $this->assertSame('1', $rows[0]['record_code']); } public function test_douyin_bill_item_filter_pages_through_ids_and_preserves_order(): void { $this->markReady(); $db = DB::connection('business_mirror'); $db->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 10, 'TouTiaoFXGZJ'), $this->row(2, 7, '2026-08-03 09:00:00', '1', 20, 'TouTiaoFXGZJ'), $this->row(3, 7, '2026-08-02 09:00:00', '1', 30, 'TouTiaoFXGZJ'), ]); $db->flushQueryLog(); $db->enableQueryLog(); $result = (new StoreReceivingDouyinBillMirrorRepository($this->repository))->paginate( ['bill_item' => '交易佣'], 'occurred_at', 'desc', 1, 2, ); $this->assertSame(['2', '3'], array_column($result['data'], 'record_code')); $queries = $db->getQueryLog(); $this->assertCount(1, array_filter($queries, static fn (array $query): bool => str_contains($query['query'], 'select "b"."Id"'))); $this->assertCount(1, array_filter($queries, static fn (array $query): bool => str_contains($query['query'], 'select "b".*') && str_contains($query['query'], 'where "b"."Id" in'))); $db->disableQueryLog(); } public function test_douyin_filter_options_only_include_the_fixed_channel(): void { $this->markReady(); $db = DB::connection('business_mirror'); $db->table('ops_jst_shop_info')->insert([ ['Id' => 10, 'shop_id' => 7, 'shop_name' => '放心购A店'], ['Id' => 11, 'shop_id' => 8, 'shop_name' => '放心购B店'], ['Id' => 12, 'shop_id' => 9, 'shop_name' => '淘系店铺'], ]); $db->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 10, 'TouTiaoFXGZJ', '交易佣金'), $this->row(2, 8, '2026-08-02 09:00:00', '1', 20, 'TouTiaoFXGZJ', '订单收入'), $this->row(3, 7, '2026-08-03 09:00:00', '1', 30, 'TouTiaoFXGZJ', '交易佣金'), $this->row(4, 9, '2026-08-04 09:00:00', '1', 40, 'alipayCSV', '淘系费用'), ]); $options = (new StoreReceivingDouyinBillMirrorRepository($this->repository))->filterOptions(); $this->assertSame(['放心购A店', '放心购B店'], $options['shops']); $this->assertSame(['交易佣金', '订单收入'], $options['bill_items']); } public function test_alipay_filter_options_match_the_visible_taobao_channel_and_platform_scope(): void { Cache::flush(); $this->markReady(); $db = DB::connection('business_mirror'); $db->table('ops_jst_shop_info')->insert([ ['Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫A店', 'shop_site' => '淘宝天猫'], ['Id' => 11, 'shop_id' => 8, 'shop_name' => '拼多多B店', 'shop_site' => '拼多多'], ['Id' => 12, 'shop_id' => 9, 'shop_name' => '天猫C店', 'shop_site' => '淘宝天猫'], ]); $db->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 10, 'alipayCSV', '交易收款'), $this->row(2, 8, '2026-08-01 10:00:00', '1', 20, 'alipayCSV', '其他平台费用'), $this->row(3, 9, '2026-08-01 11:00:00', '1', 30, 'OtherChannel', '其他渠道费用'), ]); $db->flushQueryLog(); $db->enableQueryLog(); $options = $this->repository->filterOptionsForChannel( 'alipayCSV', StoreReceivingAlipayBillMirrorRepository::DATASET, true ); $cachedOptions = $this->repository->filterOptionsForChannel( 'alipayCSV', StoreReceivingAlipayBillMirrorRepository::DATASET, true ); $this->assertSame(['天猫A店'], $options['shops']); $this->assertSame(['交易收款'], $options['bill_items']); $this->assertSame($options, $cachedOptions); $optionQueries = array_filter( $db->getQueryLog(), static fn (array $query): bool => str_contains($query['query'], 'select distinct') && (str_contains($query['query'], '"b"."shop_id"') || str_contains($query['query'], '"b"."fee_name"')), ); $this->assertCount(2, $optionQueries, '相同筛选范围只应扫描一次店铺和账单项目选项。'); $db->disableQueryLog(); } public function test_shared_dataset_keeps_channel_predicates_for_all_query_entry_points(): void { $this->markReady(); $db = DB::connection('business_mirror'); $db->table('ops_jst_shop_info')->insert([ ['Id' => 10, 'shop_id' => 7, 'shop_name' => '淘系店铺'], ['Id' => 11, 'shop_id' => 8, 'shop_name' => '其他店铺'], ]); $db->table('ops_jst_original_bill_info')->insert([ $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25, 'CommonBillTGCPay', '交易收入'), $this->row(2, 8, '2026-08-02 09:00:00', '1', 88.00, 'OtherChannel', '其他收入'), ]); $db->flushQueryLog(); $db->enableQueryLog(); $result = $this->repository->paginateChannel([], 'occurred_at', 'desc', 1, 10, 'CommonBillTGCPay'); $options = $this->repository->filterOptionsForChannel('CommonBillTGCPay'); $export = iterator_to_array($this->repository->exportChannelRows([], 'occurred_at', 'asc', 'CommonBillTGCPay')); $this->assertSame(1, $result['total']); $this->assertSame(['淘系店铺'], $options['shops']); $this->assertSame(['交易收入'], $options['bill_items']); $this->assertCount(1, $export); $channelQueries = array_filter( $db->getQueryLog(), static fn (array $query): bool => str_contains($query['query'], '"b"."channel" = ?') && in_array('CommonBillTGCPay', $query['bindings'], true), ); $this->assertGreaterThanOrEqual(5, count($channelQueries)); $db->disableQueryLog(); } private function markReady(): void { foreach ([StoreReceivingAlipayBillMirrorRepository::DATASET, StoreReceivingAlipayBillMirrorRepository::SHOP_DATASET] as $dataset) { DB::connection('business_mirror')->table('business_mirror_table_states')->insert([ 'dataset_key' => $dataset, 'initial_completed_at' => '2026-09-07 09:00:00', 'status' => 'succeeded', 'last_succeeded_at' => '2026-09-07 09:00:00', ]); } } private function row( int $id, int $shopId, string $time, string $direction, float $amount, string $channel = 'alipayCSV', string $feeName = '交易佣金', ): array { return [ 'Id' => $id, 'shop_id' => $shopId, 'channel' => $channel, 'primary_key' => 'KEY-' . $id, 'fee_name' => $feeName, 'inout_type' => $direction, 'p_auto_id' => 'P-' . $id, 'shop_i_id' => 'STYLE-' . $id, 'shop_sku_id' => 'SHOP-SKU-' . $id, 'sku_id' => 'SKU-' . $id, 'so_id' => 'ORDER-' . $id, 'combine_type' => 'double', 'fee_time' => $time, 'createTime' => '2026-08-04 09:00:00', 'updateTime' => '2026-08-04 10:00:00', 'auto_id' => 1000 + $id, 'amount' => $amount, ]; } }