dao = $dao; } /** * 从 Excel 导入物料;首行必须为:分类、商品编码、商品名称。 * 相同商品编码会更新分类和名称,以便重复导入。 */ public function importFromExcel(string $filePath): array { $sheet = IOFactory::load($filePath)->getActiveSheet(); $rows = $sheet->toArray('', true, true, false); if (count($rows) < 2) { throw new \InvalidArgumentException('导入文件至少应包含表头和一条物料数据'); } $headers = array_map(static fn ($value) => trim((string) $value), $rows[0]); $indexes = array_flip($headers); foreach (['分类', '商品编码', '商品名称'] as $header) { if (!array_key_exists($header, $indexes)) { throw new \InvalidArgumentException('导入表头必须包含:分类、商品编码、商品名称'); } } return $this->transaction(function () use ($rows, $indexes) { $created = 0; $updated = 0; $categories = []; foreach (array_slice($rows, 1) as $rowNumber => $row) { $categoryName = trim((string) ($row[$indexes['分类']] ?? '')); $productCode = trim((string) ($row[$indexes['商品编码']] ?? '')); $productName = trim((string) ($row[$indexes['商品名称']] ?? '')); if ($categoryName === '' && $productCode === '' && $productName === '') { continue; } if ($categoryName === '' || $productCode === '' || $productName === '') { throw new \InvalidArgumentException('第 ' . ($rowNumber + 2) . ' 行的分类、商品编码、商品名称均不能为空'); } $category = $categories[$categoryName] ?? null; if (!$category) { $category = MaterialCategory::query()->withTrashed()->where('category_name', $categoryName)->first(); if ($category?->trashed()) { $category->restore(); } $category ??= MaterialCategory::query()->create(['category_name' => $categoryName]); $categories[$categoryName] = $category; } $material = Material::query()->withTrashed()->where('product_code', $productCode)->first(); if ($material) { if ($material->trashed()) { $material->restore(); } $material->fill(['category_id' => $category->id, 'product_name' => $productName])->save(); $updated++; continue; } Material::query()->create([ 'category_id' => $category->id, 'product_code' => $productCode, 'product_name' => $productName, ]); $created++; } return ['created' => $created, 'updated' => $updated]; }); } }