create([ 'company_id' => 1, 'warehouse_provider' => $provider, 'warehouse' => $provider . '主仓', 'warehouse_short_name' => '测试简称', ]); $ambiguousProvider = '测试歧义仓储方-' . uniqid(); foreach (['甲', '乙'] as $name) { InventoryWarehouseNameMapping::query()->create([ 'company_id' => 1, 'warehouse_provider' => $ambiguousProvider, 'warehouse' => $ambiguousProvider . $name . '主仓', 'warehouse_short_name' => '歧义简称' . $name, ]); } $connection = (string) config('upstream_mirrors.connection', 'business_mirror'); DB::connection($connection)->table('inventory_inbound_detail_rows')->insert([ 'report_month' => '2026-08-01', 'row_key' => sha1('historic-row'), 'imported_at' => now(), ]); $path = $this->temporaryPath('supplier-summary-', '.xlsx'); $firstResult = $this->temporaryPath('supplier-result-', '.json'); $secondResult = $this->temporaryPath('supplier-result-', '.json'); try { $this->writeWorkbook($path, $provider, $ambiguousProvider); $this->artisan('inventory:supplier-summary:import', [ 'file' => $path, '--month' => '2026-09', '--result-json' => $firstResult, ]) ->expectsOutputToContain('成功=2') ->expectsOutputToContain('另有 2 条失败原因') ->assertExitCode(1); $result = json_decode((string) file_get_contents($firstResult), true); $this->assertSame('partial', $result['status']); $this->assertSame('2026-09-01', $result['report_month']); $this->assertSame(2, $result['success']); $this->assertSame(0, $result['duplicates']); $this->assertSame(12, $result['failed']); $this->assertSame(12, $result['failure_reason_count']); $this->assertSame(4, $result['failure_reasons'][0]['row']); $this->assertStringContainsString('仓库未匹配', $result['failure_reasons'][0]['reason']); $this->assertSame( '仓库未匹配到唯一仓库简称:' . $ambiguousProvider, $result['failure_reasons'][11]['reason'], ); $this->assertSame( 1, DB::connection($connection)->table('inventory_inbound_detail_rows') ->where('report_month', '2026-08-01')->count(), ); $september = DB::connection($connection)->table('inventory_inbound_detail_rows') ->where('report_month', '2026-09-01') ->get(); $this->assertCount(2, $september); $this->assertSame('BARCODE-1', $september->first()->barcode); $this->assertSame('测试简称', $september->first()->warehouse_short_name); $this->assertSame('2026-09-03 10:00:00', $september->first()->created_at_raw); $this->assertEquals(20, $september->first()->amount); $this->assertEquals(22, $september->first()->estimated_amount); $this->assertNotSame( $september[0]->row_key, $september[1]->row_key, ); $this->artisan('inventory:supplier-summary:import', [ 'file' => $path, '--month' => '2026-09', '--result-json' => $secondResult, ])->assertExitCode(1); $second = json_decode((string) file_get_contents($secondResult), true); $this->assertSame('failed', $second['status']); $this->assertSame(0, $second['success']); $this->assertSame(2, $second['duplicates']); $this->assertSame(12, $second['failed']); $this->assertSame( 2, DB::connection($connection)->table('inventory_inbound_detail_rows') ->where('report_month', '2026-09-01')->count(), ); } finally { foreach ([$path, $firstResult, $secondResult] as $file) { @unlink($file); } } } private function writeWorkbook( string $path, string $provider, string $ambiguousProvider, ): void { $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('明细'); $sheet->fromArray([ '来源单号', "单据编号\n(采购单号)", '创建时间', '供应商', '货号', '条码', '规格名', '库存变动量', '单价', '金额', '预估单价', '预估金额', '供应商条码', '备注', '仓库', '创建时间', '快递名称', '快递单号', '未发出', '负责人', ], null, 'A1'); for ($row = 2; $row <= 14; $row++) { $warehouse = $row <= 3 ? $provider : '不存在的仓库'; $sheet->fromArray([ 'SRC-1', 'PO-1', '46245', '测试供应商', 'ITEM-1', 'BARCODE-1', '测试规格', 2, 10, null, 11, null, 'SUP-BAR-1', '测试备注', $warehouse, '2026-09-03 10:00:00', '顺丰', 'SF123', '否', '测试负责人', ], null, 'A' . $row); $sheet->setCellValue('J' . $row, '=H' . $row . '*I' . $row); $sheet->setCellValue('L' . $row, '=K' . $row . '*H' . $row); } $sheet->fromArray([ 'SRC-2', 'PO-2', '46246', '测试供应商', 'ITEM-2', 'BARCODE-2', '测试规格二', 1, 10, null, 10, null, 'SUP-BAR-2', null, $ambiguousProvider, '2026-09-04 10:00:00', null, null, null, '测试负责人', ], null, 'A15'); $sheet->setCellValue('J15', '=H15*I15'); $sheet->setCellValue('L15', '=K15*H15'); (new Xlsx($spreadsheet))->save($path); $spreadsheet->disconnectWorksheets(); } private function temporaryPath(string $prefix, string $suffix): string { $temporary = tempnam(sys_get_temp_dir(), $prefix); $path = $temporary . $suffix; rename($temporary, $path); return $path; } }