'京东', 'item' => '测试货款', 'memo' => '收入', 'basis' => '收入', 'feeMemo' => '说明']; $this->assertFalse(AccountMappingStore::requiresReceiptResync($rule, $rule)); $this->assertFalse(AccountMappingStore::requiresReceiptResync($rule, array_replace($rule, ['feeMemo' => '更新说明']))); $this->assertTrue(AccountMappingStore::requiresReceiptResync($rule, array_replace($rule, ['memo' => '退款']))); $this->assertTrue(AccountMappingStore::requiresReceiptResync($rule, array_replace($rule, ['platform' => '抖音']))); } public function test_only_existing_platform_month_pairs_are_enqueued(): void { config(['upstream_pipeline.batch_worker_enabled' => true]); $schema = Schema::connection('business_mirror'); $schema->dropIfExists('store_receipt_order_match_rows'); $schema->create('store_receipt_order_match_rows', function (Blueprint $table): void { $table->string('platform'); $table->date('report_month'); }); if (! Schema::hasTable(AccountMappingResyncDispatcher::JOBS_TABLE)) { Schema::create(AccountMappingResyncDispatcher::JOBS_TABLE, function (Blueprint $table): void { $table->increments('id'); $table->string('platform'); $table->string('month'); $table->string('status'); $table->text('message')->nullable(); $table->timestamps(); }); } DB::table(AccountMappingResyncDispatcher::JOBS_TABLE)->delete(); DB::connection('business_mirror')->table('store_receipt_order_match_rows')->insert([ ['platform' => '淘系支付宝', 'report_month' => '2026-09-01'], ['platform' => '淘系微信', 'report_month' => '2026-10-01'], ]); $this->assertSame(['jobs' => 2], app(AccountMappingResyncDispatcher::class)->dispatchFor(['淘宝'])); $this->assertSame(['2026-09'], DB::table(AccountMappingResyncDispatcher::JOBS_TABLE)->where('platform', 'taobao-alipay')->pluck('month')->all()); $this->assertSame(['2026-10'], DB::table(AccountMappingResyncDispatcher::JOBS_TABLE)->where('platform', 'taobao-wechat')->pluck('month')->all()); } public function test_reimporting_unchanged_rules_does_not_invalidate_history(): void { $originalStorage = $this->app->storagePath(); $directory = sys_get_temp_dir() . '/mapping-resync-' . bin2hex(random_bytes(6)); \Illuminate\Support\Facades\File::makeDirectory($directory . '/app', 0777, true); $this->app->useStoragePath($directory); try { $store = app(AccountMappingStore::class); $store->reset(); $rows = [['platform' => '京东', 'item' => '测试资源优化项目', 'memo' => '收入']]; $this->assertSame(['京东'], $store->importRows($rows, true)['resync_platforms']); $same = $store->importRows($rows, true); $this->assertSame(0, $same['updated']); $this->assertSame([], $same['resync_platforms']); $this->assertSame([], $store->importRows([array_replace($rows[0], ['feeMemo' => '更新说明'])], true)['resync_platforms']); $this->assertSame(['京东'], $store->importRows([array_replace($rows[0], ['basis' => AccountMappingStore::BASIS_IN])], true)['resync_platforms']); } finally { $this->app->useStoragePath($originalStorage); \Illuminate\Support\Facades\File::deleteDirectory($directory); (new \ReflectionProperty(AccountMappingStore::class, 'allMemo'))->setValue(null); } } public function test_production_leaves_queue_processing_to_the_limited_batch_worker(): void { config(['upstream_pipeline.batch_worker_enabled' => true]); $dispatcher = $this->recordingDispatcher(); $dispatcher->spawnWorker(); $this->assertSame(0, $dispatcher->launches); } public function test_local_development_still_launches_an_immediate_worker(): void { config(['upstream_pipeline.batch_worker_enabled' => false]); $dispatcher = $this->recordingDispatcher(); $dispatcher->spawnWorker(); $this->assertSame(1, $dispatcher->launches); } private function recordingDispatcher(): AccountMappingResyncDispatcher { return new class extends AccountMappingResyncDispatcher { public int $launches = 0; protected function launchWorker(): void { $this->launches++; } }; } public function test_all_taobao_family_platforms_trigger_the_matching_receipt_sources(): void { $reflection = new ReflectionClass(AccountMappingResyncDispatcher::class); $map = $reflection->getConstant('PLATFORM_MAP'); $this->assertSame( [['taobao-alipay', '淘系支付宝'], ['taobao-wechat', '淘系微信']], $map['淘宝'] ); $this->assertSame( [['taobao-alipay', '淘系支付宝'], ['taobao-wechat', '淘系微信']], $map['天猫'] ); $this->assertSame([['taobao-alipay', '淘系支付宝']], $map['阿里巴巴']); } public function test_platform_without_receipt_sync_is_a_noop(): void { $result = app(AccountMappingResyncDispatcher::class)->dispatchFor(['唯品会JX']); $this->assertSame(['jobs' => 0], $result); } }