createMock(UpstreamReadOnlyQuery::class); $query->expects($this->once())->method('select')->willReturnCallback( function (string $sql, array $bindings): array { UpstreamReadOnlyQuery::assertReadOnlySql($sql); $this->assertStringContainsString('[dbo].[pm_boardInventoryDetail_snapshot]', $sql); $this->assertStringContainsString('s.stockType = ?', $sql); $this->assertStringContainsString('MAX(latest.[month])', $sql); $this->assertStringContainsString('AS stock_in_amount', $sql); $this->assertStringContainsString('AS closing_remaining_value', $sql); $this->assertStringContainsString('AS previous_remaining_value', $sql); $this->assertStringContainsString('AS stock_out_amount', $sql); $this->assertSame( ['素板', '素板', '%苏阳%', '%15密度板%', '%15MDF%', '2026-06-01', '2026-06-30', 26, 50], $bindings, ); return [[ 'manufacturer' => '苏阳', 'specification' => '15密度板', 'stock_in_amount' => '590.04', 'closing_remaining_value' => '283428.10', 'summary_opening_balance' => '12060.00', 'summary_stock_in' => '18.00', 'summary_lamination_out' => '3415.00', 'summary_inventory' => '8645.00', 'summary_remaining_value' => '507270.50', 'summary_stock_in_amount' => '590.04', 'summary_closing_remaining_value' => '283428.10', 'summary_previous_remaining_value' => '395326.80', 'summary_stock_out_amount' => '111933.70', 'total_count' => 51, ]]; }, ); $factory = $this->createMock(UpstreamReadOnlyQueryFactory::class); $factory->expects($this->once())->method('forSource')->with('jiansun')->willReturn($query); $result = (new InventoryRepository($factory))->boardInventoryDetails([ 'manufacturer' => '苏阳', 'specification' => '15密度板', 'matching_value' => '15MDF', 'date_from' => '2026-06-01', 'date_to' => '2026-06-30', ], 2, 25); $this->assertSame(51, $result['total']); $this->assertSame('苏阳', $result['data'][0]['manufacturer']); $this->assertSame(8645.0, $result['totals']['inventory']); $this->assertSame(283428.1, $result['totals']['closing_remaining_value']); $this->assertArrayNotHasKey('total_count', $result['data'][0]); $this->assertArrayNotHasKey('summary_inventory', $result['data'][0]); } public function test_board_inventory_escapes_like_filters_and_caps_page_size(): void { $query = $this->createMock(UpstreamReadOnlyQuery::class); $query->expects($this->once())->method('select')->willReturnCallback( function (string $sql, array $bindings): array { UpstreamReadOnlyQuery::assertReadOnlySql($sql); $this->assertSame(['素板', '素板', '%A\\%B\\_C%', 1, 100], $bindings); return []; }, ); $factory = $this->createMock(UpstreamReadOnlyQueryFactory::class); $factory->method('forSource')->willReturn($query); $result = (new InventoryRepository($factory))->boardInventoryDetails([ 'manufacturer' => 'A%B_C', ], 0, 1000); $this->assertSame(0, $result['total']); $this->assertSame([], $result['data']); $this->assertSame(0.0, $result['totals']['inventory']); } }