createMock(UpstreamReadOnlyQuery::class); $query->expects($this->once())->method('select')->willReturnCallback( function (string $sql, array $bindings): array { UpstreamReadOnlyQuery::assertReadOnlySql($sql); $this->assertStringContainsString('[dbo].[pm_boardPurchasePrice]', $sql); $this->assertStringContainsString('[dbo].[pm_plainPlateData]', $sql); $this->assertStringContainsString('[dbo].[pm_boardPriceChangeImport]', $sql); $this->assertStringContainsString('deduplicated_prices AS', $sql); $this->assertStringContainsString('GROUP BY number, supplier, proNumber, price', $sql); $this->assertStringContainsString('AS latest_price', $sql); $this->assertSame( ['%密度板%', '%12MDF%', '%12MDFS32%', '%冠之杰%', 26, 50], $bindings, ); return [[ 'raw_name' => '12密度板', 'raw_code' => '12MDF', 'production_code' => '12MDFS32', 'latest_price' => '23.50', 'target_supplier' => '冠之杰', 'total_count' => 51, ]]; }, ); $factory = $this->createMock(UpstreamReadOnlyQueryFactory::class); $factory->expects($this->once())->method('forSource')->with('jiansun')->willReturn($query); $result = (new RawBoardPayablesRepository($factory))->prices([ 'raw_name' => '密度板', 'raw_code' => '12MDF', 'production_code' => '12MDFS32', 'supplier' => '冠之杰', ], 2, 25); $this->assertSame(51, $result['total']); $this->assertSame('采购价', $result['data'][0]['price_type']); $this->assertSame('素板厂', $result['data'][0]['factory_type']); $this->assertArrayNotHasKey('total_count', $result['data'][0]); } public function test_reconciliations_calculate_amount_totals_and_preserve_workbook_columns(): void { $query = $this->createMock(UpstreamReadOnlyQuery::class); $query->expects($this->once())->method('select')->willReturnCallback( function (string $sql, array $bindings): array { UpstreamReadOnlyQuery::assertReadOnlySql($sql); $this->assertStringContainsString('[dbo].[finance_boardDeduction]', $sql); $this->assertStringContainsString('[dbo].[pm_boardApplyDetail]', $sql); $this->assertStringContainsString('AS reconciliation_amount', $sql); $this->assertStringContainsString('AS freight_with_allocation', $sql); $this->assertStringContainsString('AS allocated_freight', $sql); $this->assertStringContainsString('AS summary_reconciliation_amount', $sql); $this->assertStringContainsString('ISNULL(d.deliveryCount, 0) < 0', $sql); $this->assertSame([ '2026-06-01', '2026-06-30', '%12MDF%', '%密度板%', '%冠之杰%', '%苏阳%', '%JSAD\%01\_%', '%JSAD\%01\_%', 1, 100, ], $bindings); return [[ 'production_code' => '12MDFS32', 'raw_name' => '12密度板', 'outbound_quantity' => '-10.00', 'received_quantity' => '-10.00', 'unit_price' => '23.50', 'reconciliation_amount' => '-235.00', 'freight' => '0.00', 'summary_outbound_quantity' => '-10.00', 'summary_received_quantity' => '-10.00', 'summary_reconciliation_amount' => '-235.00', 'total_count' => 1, ]]; }, ); $factory = $this->createMock(UpstreamReadOnlyQueryFactory::class); $factory->expects($this->once())->method('forSource')->with('jiansun')->willReturn($query); $result = (new RawBoardPayablesRepository($factory))->reconciliations([ 'date_from' => '2026-06-01', 'date_to' => '2026-06-30', 'production_code' => '12MDF', 'raw_name' => '密度板', 'outbound_supplier' => '冠之杰', 'arrival_supplier' => '苏阳', 'outbound_no' => 'JSAD%01_', 'return_flag' => 'return', ], 0, 1000); $this->assertSame(1, $result['total']); $this->assertSame('张', $result['data'][0]['unit']); $this->assertSame('退货', $result['data'][0]['return_flag']); $this->assertSame(-235.0, $result['totals']['reconciliation_amount']); $this->assertArrayNotHasKey('summary_reconciliation_amount', $result['data'][0]); $this->assertArrayNotHasKey('total_count', $result['data'][0]); } }