[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', 'upstream_mirrors.datasets' => [ SmallPaymentWorkOrderMirrorRepository::WORK_ORDER_DATASET => [ 'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'jst_workOrderInfo', 'target' => 'fixture_work_orders', 'keys' => ['Id'], 'watermark' => 'updateTime', 'indexes' => [ ['woTypeId', 'woNo', 'Id'], ['woTypeId', 'completeTime', 'woNo', 'Id'], ], ], SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET => [ 'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'jst_small_payment_order_map', 'target' => 'fixture_payment_map', 'keys' => ['woNo'], 'watermark' => 'updated_at', 'indexes' => [['woTypeId', 'woNo']], ], ], ]); Cache::setDefaultDriver('array'); Cache::flush(); 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->string('status')->nullable(); $table->dateTime('initial_completed_at')->nullable(); $table->dateTime('last_succeeded_at')->nullable(); }); $schema->create('fixture_work_orders', function (Blueprint $table): void { $table->integer('Id'); foreach (['woNo', 'woTypeId', 'title', 'content', 'status', 'grade', 'createby', 'executors', 'completer', 'completeTime', 'created', 'updated', 'updateTime', 'businessFieldList'] as $column) { $table->text($column)->nullable(); } $table->integer('urging')->nullable(); $table->index(['woTypeId', 'woNo', 'Id']); $table->index(['woTypeId', 'completeTime', 'woNo', 'Id']); }); $schema->create('fixture_payment_map', function (Blueprint $table): void { foreach (['woNo', 'woTypeId', 'transaction_no', 'shop_name', 'payment_reason', 'title', 'status', 'work_order_create_time', 'work_order_update_time', 'parse_status', 'parse_error', 'updated_at'] as $column) { $table->text($column)->nullable(); } $table->index(['woTypeId', 'woNo']); }); foreach (array_keys(config('upstream_mirrors.datasets')) as $key) { DB::connection('business_mirror')->table('business_mirror_table_states')->insert([ 'dataset_key' => $key, 'status' => 'succeeded', 'initial_completed_at' => '2026-09-01 00:00:00', 'last_succeeded_at' => '2026-09-03 12:00:00', ]); } $type = SmallPaymentWorkOrderMirrorRepository::SMALL_PAYMENT_WORK_TYPE_ID; $workOrderDefaults = array_fill_keys([ 'Id', 'woNo', 'woTypeId', 'title', 'content', 'status', 'grade', 'urging', 'createby', 'executors', 'completer', 'completeTime', 'created', 'updated', 'updateTime', 'businessFieldList', ], null); $workOrderRows = [ [ 'Id' => 1, 'woNo' => 'GD-001', 'woTypeId' => $type, 'title' => '小额打款一', 'content' => '售后补偿', 'status' => 'CONFIRMED', 'grade' => '普通', 'urging' => 1, 'createby' => '客服甲', 'executors' => '财务甲', 'completer' => '财务甲', 'completeTime' => '2026-09-02 11:00:00', 'created' => '2026-09-02 09:00:00', 'updated' => '2026-09-02 11:00:00', 'updateTime' => '2026-09-02 11:01:00', 'businessFieldList' => json_encode([ ['fieldId' => 'SELECTField1413856620250724113227', 'value' => '支付宝账号'], ['fieldName' => '支付宝批次号', 'fieldValue' => 'BATCH-001'], ['fieldName' => '打款金额', 'fieldValue' => '50.005'], ['fieldName' => '买家账号', 'fieldValue' => 'buyer-one'], ['fieldName' => '支付宝账号', 'fieldValue' => 'account-one'], ['fieldName' => '支付宝姓名', 'fieldValue' => '测试买家'], ['fieldName' => '商品编码', 'fieldValue' => 'ITEM-001'], ['fieldName' => 'SKU编码', 'fieldValue' => 'SKU-001'], ['fieldName' => '平台', 'fieldValue' => '京东'], ['fieldName' => '实际已付金额', 'fieldValue' => '199.90'], ['fieldName' => '支付时间', 'fieldValue' => '2026-09-02 10:30:00'], ], JSON_UNESCAPED_UNICODE), ], [ 'Id' => 2, 'woNo' => 'GD-002', 'woTypeId' => $type, 'title' => '小额打款二', 'content' => '少收多收', 'status' => 'CONFIRMED', 'grade' => '紧急', 'urging' => 0, 'createby' => '客服乙', 'completer' => '财务乙', 'completeTime' => '2026-09-03 10:00:00', 'created' => '2026-08-31 09:00:00', 'updated' => '2026-09-03 10:00:00', 'updateTime' => '2026-09-03 09:31:00', 'businessFieldList' => json_encode([ ['fieldId' => 'SELECTField1413856620250724113227', 'value' => '银行卡'], ['fieldId' => 'shopField', 'type' => 'SELECT', 'value' => '18526685', 'valueLabel' => '天猫-亿家达旗舰店'], ['fieldId' => 'alipayAccountField', 'type' => 'TEXT', 'value' => 'account-two'], ['fieldId' => 'alipayNameField', 'type' => 'TEXT', 'value' => '测试买家二'], ['fieldId' => 'buyerIdField', 'type' => 'TEXT', 'value' => 'buyer-two'], ['childFieldList' => [[ ['fieldId' => 'iid', 'type' => 'TEXT', 'value' => 'ITEM-002'], ['fieldId' => 'skuId', 'type' => 'TEXT', 'value' => 'SKU-002'], ]], 'fieldId' => 'itemInfo', 'type' => 'FORM'], ['fieldId' => 'actualPaidAmountField', 'type' => 'MONEY', 'value' => '188.88'], ['fieldId' => 'platform', 'type' => 'SELECT', 'value' => '淘宝天猫'], ['fieldId' => 'payDateField', 'type' => 'DATE', 'value' => '2026-09-03 08:30:00'], ['fieldId' => 'amountField', 'type' => 'MONEY', 'value' => '20'], ['fieldId' => 'orderField', 'type' => 'TEXT', 'value' => 'ORDER-002'], ], JSON_UNESCAPED_UNICODE), ], [ 'Id' => 3, 'woNo' => 'OTHER-001', 'woTypeId' => 'OTHER', 'title' => '其他工单', 'status' => '已完成', 'created' => '2026-09-03 10:00:00', 'updated' => '2026-09-03 10:00:00', 'updateTime' => '2026-09-03 10:00:00', 'businessFieldList' => '{}', ], ]; DB::connection('business_mirror')->table('fixture_work_orders')->insert(array_map( static fn (array $row): array => array_replace($workOrderDefaults, $row), $workOrderRows, )); DB::connection('business_mirror')->table('fixture_payment_map')->insert([ [ 'woNo' => 'GD-001', 'woTypeId' => $type, 'transaction_no' => 'ORDER-001', 'shop_name' => '京东旗舰店', 'payment_reason' => '售后补偿', 'title' => '小额打款一', 'status' => '已完成', 'work_order_create_time' => '2026-09-02 09:00:00', 'work_order_update_time' => '2026-09-02 11:00:00', 'parse_status' => 'success', 'updated_at' => '2026-09-02 11:01:00', ], [ 'woNo' => 'GD-002', 'woTypeId' => $type, 'transaction_no' => 'ORDER-002', 'shop_name' => '天猫旗舰店', 'payment_reason' => '少收多收', 'title' => '小额打款二', 'status' => 'CONFIRMED', 'work_order_create_time' => '2026-08-31 09:00:00', 'work_order_update_time' => '2026-09-03 09:30:00', 'parse_status' => 'success', 'updated_at' => '2026-09-03 09:31:00', ], [ 'woNo' => 'OTHER-001', 'woTypeId' => 'OTHER', 'transaction_no' => 'BATCH-003', 'shop_name' => '其他店铺', 'payment_reason' => '其他', 'title' => '其他工单', 'status' => '已完成', 'work_order_create_time' => '2026-09-03 10:00:00', 'work_order_update_time' => '2026-09-03 10:00:00', 'parse_status' => 'success', 'updated_at' => '2026-09-03 10:00:00', ], ]); $this->repository = new SmallPaymentWorkOrderMirrorRepository( new BusinessMirrorDatasetRegistry(), new GenericBusinessMirrorRepository(app('db')), ); } public function test_filters_paginates_and_parses_small_payment_business_fields(): void { $result = $this->repository->paginate($this->filters([ 'shop_name' => '京东', 'online_order_no' => 'ORDER-001', 'status' => '已完成', ]), 1, 25); $this->assertSame(1, $result['total']); $this->assertSame('50.01', $result['page_payment_amount']); $this->assertSame('50.01', $result['filtered_payment_amount']); $this->assertSame('GD-001', $result['data'][0]['work_order_no']); $this->assertSame('BATCH-001', $result['data'][0]['alipay_batch_no']); $this->assertSame('已完成', $result['data'][0]['work_order_status']); $this->assertSame('小额打款', $result['data'][0]['work_order_type']); $this->assertSame('支付宝已打款', $result['data'][0]['payment_status']); $this->assertSame('ORDER-001', $result['data'][0]['online_order_no']); $this->assertSame('SKU-001', $result['data'][0]['sku_code']); $this->assertSame('199.90', $result['data'][0]['actual_paid_amount']); $this->assertArrayNotHasKey('business_field_list', $result['data'][0]); } public function test_stable_order_second_page_and_exact_work_type_filter(): void { $result = $this->repository->paginate($this->filters(), 2, 1); $this->assertSame(2, $result['total']); $this->assertSame('GD-001', $result['data'][0]['work_order_no']); $this->assertSame('50.01', $result['page_payment_amount']); $this->assertSame('70.01', $result['filtered_payment_amount']); } public function test_export_uses_same_filters_and_parsed_shape(): void { $rows = iterator_to_array($this->repository->exportRows($this->filters([ 'transaction_no' => 'BATCH-001', 'payment_reason' => '售后', ]), 1)); $this->assertCount(1, $rows); $this->assertSame('GD-001', $rows[0]['work_order_no']); $this->assertSame('BATCH-001', $rows[0]['alipay_batch_no']); $this->assertSame('ORDER-001', $rows[0]['online_order_no']); $this->assertSame('已完成', $rows[0]['work_order_status']); $this->assertSame('支付宝已打款', $rows[0]['payment_status']); } public function test_completion_date_and_chinese_status_follow_upstream_semantics(): void { $result = $this->repository->paginate($this->filters([ 'from' => '2026-09-03', 'to' => '2026-09-03', 'status' => '已完成', ]), 1, 25); $this->assertSame(1, $result['total']); $this->assertSame('GD-002', $result['data'][0]['work_order_no']); $this->assertSame('2026-08-31 09:00:00', $result['data'][0]['work_order_created_at']); $this->assertSame('2026-09-03 10:00:00', $result['data'][0]['completed_at']); $this->assertSame('已完成', $result['data'][0]['work_order_status']); $this->assertSame('银行卡已打款', $result['data'][0]['payment_status']); $this->assertSame('', $result['data'][0]['alipay_batch_no']); } public function test_derived_payment_status_filter_matches_payment_method_field(): void { $result = $this->repository->paginate($this->filters([ 'status' => '支付宝已打款', ]), 1, 25); $this->assertSame(1, $result['total']); $this->assertSame('GD-001', $result['data'][0]['work_order_no']); $this->assertSame('支付宝已打款', $result['data'][0]['payment_status']); } public function test_datetime_minute_bounds_include_the_selected_end_minute(): void { $result = $this->repository->paginate($this->filters([ 'from' => '2026-09-03T10:00', 'to' => '2026-09-03T10:00', ]), 1, 25); $this->assertSame(1, $result['total']); $this->assertSame('GD-002', $result['data'][0]['work_order_no']); } public function test_summary_and_filter_options_reuse_cached_results(): void { $scanQueries = []; DB::connection('business_mirror')->listen(static function ($query) use (&$scanQueries): void { $sql = strtolower((string) $query->sql); if (str_contains($sql, 'businessfieldlist') || str_contains($sql, 'select distinct')) { $scanQueries[] = $sql; } }); $filters = $this->filters(); $firstAmount = $this->repository->filteredPaymentAmount($filters); $firstOptions = $this->repository->filterOptions(); $queriesAfterFirstRead = count($scanQueries); $secondAmount = $this->repository->filteredPaymentAmount($filters); $secondOptions = $this->repository->filterOptions(); $this->assertSame('70.01', $firstAmount); $this->assertSame($firstAmount, $secondAmount); $this->assertSame($firstOptions, $secondOptions); $this->assertSame(['京东旗舰店', '天猫旗舰店'], $firstOptions['shops']); $this->assertSame(['售后补偿', '少收多收'], $firstOptions['payment_reasons']); $this->assertGreaterThan(0, $queriesAfterFirstRead); $this->assertSame($queriesAfterFirstRead, count($scanQueries)); } public function test_unready_state_is_explicit(): void { DB::connection('business_mirror')->table('business_mirror_table_states') ->where('dataset_key', SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET) ->update(['initial_completed_at' => null, 'status' => 'running']); $states = $this->repository->states(); $status = $this->repository->syncStatus($states); $this->assertFalse($this->repository->ready()); $this->assertSame('initializing', $status['code']); $this->expectException(\RuntimeException::class); $this->repository->paginate($this->filters(), 1, 25); } /** @param array $overrides @return array */ private function filters(array $overrides = []): array { return array_replace([ 'from' => '2026-09-01', 'to' => '2026-09-30', 'work_order_no' => '', 'shop_name' => '', 'transaction_no' => '', 'online_order_no' => '', 'status' => '', 'payment_reason' => '', ], $overrides); } }