[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', 'upstream_mirrors.datasets' => [ CourierFreightReconciliationMirrorRepository::DATASET => [ 'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'fys_express_courier_settlement', 'target' => 'js_express_courier_settlement', 'keys' => ['Id'], 'watermark' => 'modify_time', ], ], ]); DB::purge('business_mirror'); Schema::connection('business_mirror')->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->string('run_mode')->nullable(); }); Schema::connection('business_mirror')->create('js_express_courier_settlement', function (Blueprint $table): void { $table->integer('Id'); foreach (['month', 'status', 'courier_company_name', 'expresscode', 'expressname', 'replenish_courier', 'reconciliation_person', 'file_status', 'taskid', 'guid', 'creator'] as $column) { $table->string($column)->nullable(); } foreach (['opening_balance', 'courier_provided_amount', 'reconciliation_difference', 'confirmed_amount', 'advance_payment', 'payable_amount', 'actual_payment', 'compensation_amount', 'other_replenishment', 'corporate_payment_amount', 'personal_payment_amount'] as $column) { $table->decimal($column, 20, 6)->nullable(); } $table->string('waybill')->nullable(); $table->dateTime('create_time')->nullable(); $table->dateTime('modify_time')->nullable(); $table->boolean('is_delete')->nullable(); }); $this->repository = new CourierFreightReconciliationMirrorRepository( new BusinessMirrorDatasetRegistry(), new GenericBusinessMirrorRepository(app('db')), ); } public function test_it_rejects_an_unready_mirror(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('courier_freight_reconciliation_mirror_not_ready'); $this->repository->paginate([], 1, 25); } public function test_it_filters_sorts_and_summarizes_only_approved_mirror_fields(): void { DB::connection('business_mirror')->table('business_mirror_table_states')->insert([ 'dataset_key' => CourierFreightReconciliationMirrorRepository::DATASET, 'initial_completed_at' => '2026-09-01 09:00:00', 'status' => 'succeeded', 'last_succeeded_at' => now()->toDateTimeString(), ]); DB::connection('business_mirror')->table('js_express_courier_settlement')->insert([ $this->row(1, 'YT-001', '圆通速递', 100, 80, 20, '2026-08'), $this->row(2, 'YT-002', '圆通速递', 50, 50, 0, '2026-08'), $this->row(3, 'ZT-003', '中通礼品单', 30, 25, 5, '2026-09'), ]); $result = $this->repository->paginate([ 'express_no' => 'YT', 'express_name' => '圆通', 'month' => '2026-08', ], 1, 1); $this->assertSame(2, $result['total']); $this->assertSame('YT-002', $result['data'][0]['waybill']); $this->assertSame(150.0, $result['summary']['payable_amount']); $this->assertSame(130.0, $result['summary']['actual_payment']); $this->assertSame(20.0, $result['summary']['reconciliation_difference']); $this->assertSame('fresh', $result['sync']['code']); $this->assertArrayNotHasKey('password', $result['data'][0]); } /** @return array */ private function row(int $id, string $waybill, string $express, float $payable, float $actual, float $difference, string $month): array { return [ 'Id' => $id, 'waybill' => $waybill, 'expressname' => $express, 'courier_company_name' => $express, 'month' => $month, 'status' => '已确认', 'payable_amount' => $payable, 'actual_payment' => $actual, 'confirmed_amount' => $payable, 'reconciliation_difference' => $difference, 'create_time' => '2026-09-07 09:00:00', 'modify_time' => '2026-09-07 10:00:00', ]; } }