createMock(UpstreamReadOnlyQuery::class); $call = 0; $query->expects($this->exactly(2))->method('select')->willReturnCallback( function (string $sql, array $bindings) use (&$call): array { $call++; UpstreamReadOnlyQuery::assertReadOnlySql($sql); if ($call === 2) { $this->assertStringContainsString('[rpt].[jst_returnAndRefundInfo]', $sql); $this->assertSame(['1'], $bindings); return [[ 'internal_order_no' => '1', 'after_sales_id' => 'AF-1', 'after_sales_time' => '2026-08-02 12:00:00', 'sub_order_no' => 'SUB-1', 'product_code' => 'SKU-1', 'application_quantity' => '1', 'application_amount' => '6.00', 'return_quantity' => '1', 'return_amount' => '6.00', ]]; } $this->assertStringContainsString('[rpt].[jst_Orders]', $sql); $this->assertStringContainsString('[rpt].[jst_OrderItem]', $sql); $this->assertStringContainsString('OFFSET 2 ROWS FETCH NEXT 3 ROWS ONLY', $sql); $this->assertStringNotContainsString('COUNT(*) OVER', $sql); $this->assertSame([ '2026-08-01', '2026-08-02', '%SO\%\_%', '%SO\%\_%', '%SO\%\_%', '%JD%', '%SKU%', '%SKU%', '%SKU%', ], $bindings); return [[ 'internal_order_no' => '1', 'sub_order_no' => 'SUB-1', 'product_code' => 'SKU-1', 'shop_site' => 'jd', 'shop_style_code' => '1001', 'send_time' => '2026-08-02 10:00:00', 'unit_cost' => '3.00', 'product_amount' => '12.34', 'product_quantity' => '2', 'actual_shipped_amount' => '12.34', 'actual_shipped_quantity' => '2', 'actual_shipped_cost' => '6.00', 'pre_application_quantity' => '0', 'pre_application_amount' => '0', 'pre_application_cost' => '0', 'pre_return_quantity' => '0', 'pre_return_amount' => '0', 'pre_return_cost' => '0', 'pre_refund_amount' => '0', 'pre_refund_cost' => '0', 'pre_platform_subsidy' => '0', 'post_application_quantity' => '0', 'post_application_amount' => '0', 'post_application_cost' => '0', 'post_return_quantity' => '0', 'post_return_amount' => '0', 'post_return_cost' => '0', 'post_refund_amount' => '0', 'post_refund_cost' => '0', 'post_platform_subsidy' => '0', 'defective_quantity' => '0', ]]; }, ); $result = (new OrderDataRepository($this->factory($query)))->afterSales([ 'from' => '2026-08-01', 'to' => '2026-08-02', 'order_no' => 'SO%_', 'shop_name' => 'JD', 'sku_id' => 'SKU', ], 2, 2); $this->assertCount(1, $result['rows']); $this->assertSame(12.34, $result['rows'][0]['product_amount']); $this->assertSame(2.0, $result['rows'][0]['product_quantity']); $this->assertSame('https://item.jd.com/1001.html', $result['rows'][0]['product_link']); $this->assertSame('AF-1', $result['rows'][0]['after_sales_no']); $this->assertSame(1.0, $result['rows'][0]['post_return_quantity']); $this->assertNull($result['rows'][0]['account_500112']); $this->assertNull($result['rows'][0]['account_8001201']); } public function test_promotion_expenses_use_one_statistics_date_and_map_only_the_page_rows(): void { $query = $this->createMock(UpstreamReadOnlyQuery::class); $call = 0; $query->expects($this->exactly(2))->method('select')->willReturnCallback( function (string $sql, array $bindings) use (&$call): array { $call++; UpstreamReadOnlyQuery::assertReadOnlySql($sql); if ($call === 2) { $this->assertStringContainsString('[rpt].[jst_skumap]', $sql); $this->assertSame(['ITEM-1'], $bindings); return [[ 'shop_style_code' => 'ITEM-1', 'shop_name' => 'JD', 'product_code' => 'SKU-1', 'style_code' => 'STYLE-1', 'shop_product_code' => 'SHOP-SKU-1', 'online_color_spec' => '黑色', ]]; } $this->assertStringContainsString('[rpt].[ads_itemid_sales_cost_ratio_v2_di]', $sql); $this->assertStringContainsString('OFFSET 3 ROWS FETCH NEXT 4 ROWS ONLY', $sql); $this->assertStringContainsString('FROM paged p', $sql); $this->assertStringNotContainsString('SUM(c.[push_fee])', $sql); $this->assertStringNotContainsString('COUNT(*) OVER', $sql); $this->assertSame([ '2026-08-09', '%JD%', '%SKU\_%', '%SKU\_%', '%SKU\_%', '%SKU\_%', ], $bindings); return [[ 'shop_name' => 'JD', 'shop_style_code' => 'ITEM-1', 'promotion_fee' => '9.05', ]]; }, ); $result = (new OrderDataRepository($this->factory($query)))->promotionExpenses([ 'date' => '2026-08-09', 'shop_name' => 'JD', 'sku_id' => 'SKU_', ], 2, 3); $this->assertSame('2026-08-09', $result['date']); $this->assertSame(9.05, $result['rows'][0]['promotion_fee']); $this->assertSame('SKU-1', $result['rows'][0]['product_code']); } private function factory(UpstreamReadOnlyQuery $query): UpstreamReadOnlyQueryFactory { $factory = $this->createMock(UpstreamReadOnlyQueryFactory::class); $factory->expects($this->once()) ->method('forSource') ->with('operations_replica') ->willReturn($query); return $factory; } }