['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''], 'upstream_mirrors.connection' => 'business_mirror', ]); DB::purge('business_mirror'); $this->createSchema(); $this->seedRows(); } /** @return iterable}> */ public static function filterProvider(): iterable { yield 'no filter' => [[]]; yield 'date range' => [['created_from' => '2026-10-03', 'created_to' => '2026-10-06']]; yield 'supplier' => [['supplier_name' => '供应商A']]; yield 'shop' => [['shop_name' => '店铺甲']]; yield 'keyword' => [['keyword' => '77']]; yield 'material' => [['material_no' => 'SKU-2']]; yield 'ding status only' => [['status' => 'COMPLETED']]; yield 'purchase status only' => [['status' => 'FINISHED']]; yield 'product id' => [['product_id' => 'PID-1']]; } /** @dataProvider filterProvider */ public function test_pages_match_the_fully_merged_order_totals_and_summary(array $filters): void { $repository = app(DingOnlineDaifaPriceMirrorRepository::class); $expected = array_map( static fn (array $row): string => $row['source_type'] . ':' . $row['process_instance_id'] . ':' . $row['detail_index'], iterator_to_array($repository->exportRows($filters), false), ); $perPage = 4; $first = $repository->paginate($filters, 1, $perPage); $this->assertSame(count($expected), $first['total']); $actual = []; $pages = max((int) ceil($first['total'] / $perPage), 1) + 1; // one empty page past the end for ($page = 1; $page <= $pages; $page++) { foreach ($repository->paginate($filters, $page, $perPage)['data'] as $row) { $actual[] = $row['source_type'] . ':' . $row['process_instance_id'] . ':' . $row['detail_index']; } } $this->assertSame($expected, $actual); $exported = iterator_to_array($repository->exportRows($filters), false); $ding = array_values(array_filter($exported, static fn (array $r): bool => $r['source_type'] === 'ding')); $summary = $first['summary']; $this->assertSame(count($exported), $summary['count']); $this->assertSame(count($exported) - count($ding), $summary['purchase_count']); $this->assertSame(count($ding), $summary['matchable_count']); $this->assertSame( count(array_filter($ding, static fn (array $r): bool => ($r['matched_product']['matched'] ?? false) === true)), $summary['matched_count'], ); $this->assertSame(round(array_sum(array_column($ding, 'daifa_amount')), 2), $summary['daifa_amount']); $this->assertSame(round(array_sum(array_column($ding, 'paid_amount')), 2), $summary['paid_amount']); $this->assertSame(round(array_sum(array_column($ding, 'price_diff')), 2), $summary['price_diff']); } public function test_page_rows_keep_their_full_fields(): void { $data = app(DingOnlineDaifaPriceMirrorRepository::class)->paginate([], 1, 100)['data']; $ding = collect($data)->first(fn (array $r): bool => $r['source_type'] === 'ding' && $r['process_instance_id'] === 'D1' && $r['detail_index'] === 2); $this->assertSame('PID-1', $ding['product_id']); $this->assertSame('供应商A', $ding['supplier_name']); $this->assertSame(['status' => 'COMPLETED', 'result' => 'agree'], $ding['workflow']); $purchase = collect($data)->first(fn (array $r): bool => $r['source_type'] === 'purchase' && $r['detail_index'] === 110); $this->assertSame(9000, $purchase['purchase_order_id']); $this->assertSame('店铺甲', $purchase['shop_name']); $this->assertSame('77001', $purchase['shop_trade_no']); $this->assertSame('已完成', $purchase['status_label']); } private function createSchema(): void { $schema = Schema::connection('business_mirror'); $schema->create('business_mirror_table_states', function (Blueprint $table): void { $table->string('dataset_key')->primary(); $table->string('status'); $table->dateTime('initial_completed_at')->nullable(); $table->dateTime('last_succeeded_at')->nullable(); }); $schema->create(DingOnlineDaifaPriceMirrorRepository::TARGET_TABLE, function (Blueprint $table): void { $table->string('process_instance_id'); $table->unsignedInteger('detail_index'); $table->string('product_id')->nullable(); $table->string('product_name')->nullable(); $table->string('daifa_trade_no')->nullable(); $table->string('shop_trade_no')->nullable(); $table->string('shop_name')->nullable(); $table->string('payee_name')->nullable(); $table->string('supplier_name')->nullable(); $table->string('material_no')->nullable(); $table->integer('ship_qty')->nullable(); $table->decimal('paid_amount', 20, 2)->nullable(); $table->decimal('daifa_amount', 20, 2)->nullable(); $table->decimal('price_diff', 20, 2)->nullable(); $table->text('workflow_payload')->nullable(); $table->text('scheme_payload')->nullable(); $table->text('task_payload')->nullable(); $table->string('workflow_created_at', 32)->nullable(); }); $schema->create('product_purchase_price_sync_states', function (Blueprint $table): void { $table->string('dataset_key')->primary(); $table->string('status'); $table->unsignedTinyInteger('projection_version')->default(1); $table->dateTime('initial_completed_at')->nullable(); $table->dateTime('last_succeeded_at')->nullable(); }); $schema->create('product_purchase_price_mirror_rows', function (Blueprint $table): void { $table->unsignedBigInteger('item_id')->primary(); $table->unsignedBigInteger('purchase_order_id'); $table->unsignedBigInteger('source_order_no')->nullable(); $table->string('supplier_name')->nullable(); $table->string('sku_id')->nullable(); $table->string('style_id')->nullable(); $table->decimal('quantity', 20, 4)->nullable(); $table->decimal('unit_price', 20, 4)->nullable(); $table->string('document_status')->nullable(); $table->text('labels')->nullable(); $table->dateTime('document_created_at')->nullable(); $table->dateTime('finished_at')->nullable(); $table->dateTime('purchase_updated_at'); }); $schema->create('ops_jst_orders', function (Blueprint $table): void { $table->unsignedBigInteger('Id')->primary(); $table->unsignedBigInteger('o_id')->index(); $table->string('so_id')->nullable()->index(); $table->string('shop_name')->nullable(); }); } private function seedRows(): void { $db = DB::connection('business_mirror'); $db->table('business_mirror_table_states')->insert([ [ 'dataset_key' => DingOnlineDaifaPriceMirrorRepository::DATASET_KEY, 'status' => 'succeeded', 'initial_completed_at' => '2026-10-01 00:00:00', 'last_succeeded_at' => '2026-10-28 09:00:00', ], [ 'dataset_key' => 'operations_replica.rpt.jst_Orders', 'status' => 'succeeded', 'initial_completed_at' => '2026-10-01 00:00:00', 'last_succeeded_at' => '2026-10-28 09:00:00', ], ]); $db->table('product_purchase_price_sync_states')->insert([ 'dataset_key' => ProductPurchasePriceMirrorRepository::DATASET_KEY, 'status' => 'succeeded', 'projection_version' => ProductPurchasePriceMirrorRepository::CURRENT_PROJECTION_VERSION, 'initial_completed_at' => '2026-10-01 00:00:00', 'last_succeeded_at' => '2026-10-28 10:00:00', ]); $ding = []; // 11 张审批、每张 1~3 条明细;部分创建时间与采购单完全相同,用来检验同一时间的先后。 for ($i = 1; $i <= 11; $i++) { for ($d = 1; $d <= ($i % 3) + 1; $d++) { $ding[] = [ 'process_instance_id' => 'D' . $i, 'detail_index' => $d, 'product_id' => $i % 2 === 0 ? '' : 'PID-' . ($i % 4), 'product_name' => $i % 5 === 0 ? null : '商品' . $i, 'daifa_trade_no' => 'DF' . (770 + $i), 'shop_trade_no' => 'SO' . (100 + $i), 'shop_name' => $i % 2 === 0 ? '店铺甲' : '店铺乙', 'payee_name' => '收款人', 'supplier_name' => $i % 3 === 0 ? '供应商B' : '供应商A', 'material_no' => 'SKU-' . ($d + $i % 2), 'ship_qty' => 1, 'paid_amount' => 10 + $i + $d / 10, 'daifa_amount' => 8 + $i, 'price_diff' => 2 + $d / 10, 'workflow_payload' => '{"status":"COMPLETED","result":"agree"}', 'scheme_payload' => '[]', 'task_payload' => '[]', 'workflow_created_at' => sprintf('2026-10-%02d 10:00:00', ($i % 7) + 1), ]; } } $db->table(DingOnlineDaifaPriceMirrorRepository::TARGET_TABLE)->insert($ding); $db->table('ops_jst_orders')->insert([ ['Id' => 1, 'o_id' => 5001, 'so_id' => '77001', 'shop_name' => '店铺甲'], ['Id' => 2, 'o_id' => 5002, 'so_id' => '88002', 'shop_name' => '店铺乙'], ]); $purchase = []; $statuses = ['Confirmed', 'Finished', 'Cancelled']; for ($i = 1; $i <= 14; $i++) { $purchase[] = [ 'item_id' => 100 + $i, // 9001 与 10001 一起检验单号按文本排序(与原合并口径一致) 'purchase_order_id' => $i % 4 === 0 ? 10000 + $i : 9000 + ($i % 5), 'source_order_no' => $i % 3 === 0 ? null : 5000 + ($i % 2) + 1, 'supplier_name' => $i % 2 === 0 ? '供应商A' : '供应商C', 'sku_id' => 'SKU-' . ($i % 3), 'quantity' => 1, 'unit_price' => 5 + $i, 'document_status' => $statuses[$i % 3], 'document_created_at' => $i % 6 === 0 ? null : sprintf('2026-10-%02d 10:00:00', ($i % 7) + 1), 'purchase_updated_at' => sprintf('2026-10-%02d 09:00:00', ($i % 8) + 1), ]; } $db->table('product_purchase_price_mirror_rows')->insert($purchase); } }