[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', 'upstream_mirrors.datasets' => [ WorkOrderMirrorRepository::MAIN_DATASET => $this->definition('work_order_main', 'fixture_work_order_main', ['id'], 'update_time'), WorkOrderMirrorRepository::SUB_DATASET => $this->definition('work_order_sub', 'fixture_work_order_sub', ['id'], 'update_time'), WorkOrderMirrorRepository::CONFIG_DATASET => $this->definition('work_order_form_config', 'fixture_work_order_config', ['id'], 'update_time'), WorkOrderMirrorRepository::VALUE_DATASET => $this->definition('work_order_form_value', 'fixture_work_order_values', ['id'], 'create_time'), ], ]); 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(); }); $this->createTables($schema); 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-08-01 00:00:00', 'last_succeeded_at' => '2026-08-31 12:00:00', ]); } DB::connection('business_mirror')->table('fixture_work_order_config')->insert([ ['id' => 1, 'work_type' => 'WAREHOUSE', 'work_type_name' => '仓内未发', 'group_name' => '协同工单', 'is_enabled' => 1], ['id' => 2, 'work_type' => 'OTHER', 'work_type_name' => '其他', 'group_name' => '其他', 'is_enabled' => 1], ]); DB::connection('business_mirror')->table('fixture_work_order_main')->insert([ ['id' => 1, 'work_type' => 'WAREHOUSE', 'order_no' => 'WO-001', 'title' => '仓内未发一', 'status' => '待处理', 'create_user' => '张三', 'create_time' => '2026-08-20 10:00:00', 'update_time' => '2026-08-20 10:00:00', 'is_deleted' => 0], ['id' => 2, 'work_type' => 'WAREHOUSE', 'order_no' => 'WO-002', 'title' => '仓内未发二', 'status' => '处理中', 'create_user' => '李四', 'create_time' => '2026-08-21 10:00:00', 'update_time' => '2026-08-21 10:00:00', 'is_deleted' => 0], ['id' => 3, 'work_type' => 'OTHER', 'order_no' => 'WO-003', 'title' => '其他类型', 'status' => '待处理', 'create_user' => '张三', 'create_time' => '2026-08-22 10:00:00', 'update_time' => '2026-08-22 10:00:00', 'is_deleted' => 0], ['id' => 4, 'work_type' => 'WAREHOUSE', 'order_no' => 'WO-004', 'title' => '已删除', 'status' => '待处理', 'create_user' => '张三', 'create_time' => '2026-08-23 10:00:00', 'update_time' => '2026-08-23 10:00:00', 'is_deleted' => 1], ]); DB::connection('business_mirror')->table('fixture_work_order_sub')->insert([ ['id' => 1, 'sub_order_no' => 'SUB-001', 'main_order_no' => 'WO-002', 'title' => '拣货任务', 'assign_user' => '王五', 'status' => '执行中', 'is_deleted' => 0], ['id' => 2, 'sub_order_no' => 'SUB-002', 'main_order_no' => 'WO-002', 'title' => '已删除任务', 'assign_user' => '赵六', 'status' => '已取消', 'is_deleted' => 1], ]); DB::connection('business_mirror')->table('fixture_work_order_values')->insert([ ['id' => 1, 'main_order_no' => 'WO-002', 'field_code' => 'reason', 'field_name' => '未发原因', 'field_value' => '缺货', 'field_type' => 'text', 'sort' => 1, 'is_deleted' => 0], ['id' => 2, 'main_order_no' => 'WO-002', 'field_code' => 'secret', 'field_name' => '删除字段', 'field_value' => '不应显示', 'field_type' => 'text', 'sort' => 2, 'is_deleted' => 1], ]); $this->repository = new WorkOrderMirrorRepository( new BusinessMirrorDatasetRegistry(), new GenericBusinessMirrorRepository(app('db')), ); } public function test_default_type_delete_filter_pagination_and_related_rows(): void { $page = $this->repository->paginate(1, 1); $this->assertTrue($page['ready']); $this->assertSame(2, $page['total']); $this->assertSame('WO-002', $page['data'][0]['order_no']); $this->assertCount(1, $page['data'][0]['sub_orders']); $this->assertSame('SUB-001', $page['data'][0]['sub_orders'][0]['sub_order_no']); $this->assertCount(1, $page['data'][0]['form_values']); $this->assertSame('缺货', $page['data'][0]['form_values'][0]['field_value']); $filtered = $this->repository->paginate(1, 50, ['assign_user' => '王五']); $this->assertSame(1, $filtered['total']); $this->assertSame('WO-002', $filtered['data'][0]['order_no']); $secondPage = $this->repository->paginate(2, 1); $this->assertSame('WO-001', $secondPage['data'][0]['order_no']); } public function test_after_sales_alerts_are_filtered_by_type_and_form_values(): void { DB::connection('business_mirror')->table('fixture_work_order_config')->insert([ 'id' => 3, 'work_type' => 'ORDER_AFSALES', 'work_type_name' => '天猫_售后超标', 'group_name' => '运营', 'is_enabled' => 1, ]); DB::connection('business_mirror')->table('fixture_work_order_main')->insert([ 'id' => 5, 'work_type' => 'ORDER_AFSALES', 'order_no' => 'WO-005-AF', 'title' => '售后超标提醒', 'status' => '待处理', 'create_user' => '运营一组', 'create_time' => '2026-08-24 10:00:00', 'update_time' => '2026-08-24 10:00:00', 'is_deleted' => 0, ]); DB::connection('business_mirror')->table('fixture_work_order_values')->insert([ ['id' => 3, 'main_order_no' => 'WO-005-AF', 'field_code' => 'shop_name', 'field_name' => '店铺名称', 'field_value' => '天猫旗舰店', 'field_type' => 'text', 'sort' => 1, 'is_deleted' => 0], ['id' => 4, 'main_order_no' => 'WO-005-AF', 'field_code' => 'id', 'field_name' => '商品ID', 'field_value' => 'SKU-AF-1', 'field_type' => 'text', 'sort' => 2, 'is_deleted' => 0], ]); $result = $this->repository->paginateAfterSales(1, 10, [ 'from' => '2026-08-24', 'to' => '2026-08-24', 'shop_name' => '旗舰店', 'sku_id' => 'SKU-AF', ]); $this->assertTrue($result['ready']); $this->assertSame(1, $result['total']); $this->assertSame('WO-005-AF', $result['data'][0]['order_no']); $this->assertSame('天猫_售后超标', $result['data'][0]['work_type_name']); $this->assertSame('天猫旗舰店', $result['data'][0]['form_values'][0]['field_value']); } public function test_unready_state_is_explicit_and_never_looks_like_empty_data(): void { DB::connection('business_mirror')->table('business_mirror_table_states') ->where('dataset_key', WorkOrderMirrorRepository::SUB_DATASET) ->update(['initial_completed_at' => null, 'status' => 'running']); $result = $this->repository->paginate(1, 25); $this->assertFalse($result['ready']); $this->assertSame([], $result['data']); $this->assertSame('initial_sync_pending', $result['sync_status']['code']); $this->assertFalse($result['sync_status']['datasets']['sub']['ready']); } private function definition(string $object, string $target, array $keys, ?string $watermark): array { return [ 'source_key' => 'operations_replica', 'schema' => 'rpt', 'object' => $object, 'target' => $target, 'keys' => $keys, 'watermark' => $watermark, ]; } private function createTables($schema): void { $schema->create('fixture_work_order_main', function (Blueprint $table): void { $table->integer('id'); foreach (['work_type', 'order_no', 'title', 'status', 'create_user', 'expect_minute', 'submit_time', 'expect_end_time', 'end_time', 'score', 'update_user', 'description', 'create_time', 'update_time'] as $column) { $table->string($column)->nullable(); } $table->integer('is_deleted')->nullable(); }); $schema->create('fixture_work_order_sub', function (Blueprint $table): void { $table->integer('id'); foreach (['sub_order_no', 'main_order_no', 'title', 'assign_user', 'status', 'expect_minute', 'submit_time', 'expect_end_time', 'finish_time', 'content', 'notify_submit', 'notify_soon', 'notify_overdue', 'overdue_notify_count', 'create_time', 'update_time', 'taskid'] as $column) { $table->string($column)->nullable(); } $table->integer('is_deleted')->nullable(); }); $schema->create('fixture_work_order_config', function (Blueprint $table): void { $table->integer('id'); foreach (['work_type', 'work_type_name', 'group_name', 'icon', 'icon_color', 'time_limit_options', 'form_fields', 'sort', 'remark', 'create_time', 'update_time', 'timeout_reminder_count'] as $column) { $table->string($column)->nullable(); } $table->integer('is_enabled')->nullable(); }); $schema->create('fixture_work_order_values', function (Blueprint $table): void { $table->integer('id'); foreach (['main_order_no', 'field_code', 'field_name', 'field_value', 'field_type', 'sort', 'create_time'] as $column) { $table->string($column)->nullable(); } $table->integer('is_deleted')->nullable(); }); } }