argument('file'); $connection = (string) config('upstream_mirrors.connection', 'business_mirror'); $table = 'inventory_inbound_detail_rows'; $forcedMonth = trim((string) ($this->option('month') ?? '')); $now = now(); $maps = $this->shortNameMap(); $this->info('仓库名称映射:仓库名 ' . count($maps['warehouse']) . ' 个、仓储方 ' . count($maps['provider']) . ' 个可唯一匹配'); // 原样导入:以本次文件为准整体重建,不做去重、不做加工。 DB::connection($connection)->table($table)->delete(); $reader = IOFactory::createReaderForFile($file); $reader->setReadDataOnly(true); $sheet = $reader->load($file)->getSheet(0); $batch = []; $imported = 0; $mapped = 0; $skipped = 0; foreach ($sheet->getRowIterator(2) as $row) { $rowNumber = $row->getRowIndex(); $cells = []; foreach ($row->getCellIterator('A', 'U') as $cell) { $cells[] = $cell->getFormattedValue(); } $warehouse = trim((string) ($cells[14] ?? '')); $createdRaw = trim((string) ($cells[16] ?? '')); if ($warehouse === '' && $createdRaw === '') { $skipped++; continue; } $month = $forcedMonth !== '' ? $forcedMonth : $this->monthOf($createdRaw); if ($month === '') { $skipped++; continue; } // 一般用"仓库"匹配仓库简称;含"代发仓"字样的按"仓储方"匹配。 $shortName = ''; if ($warehouse !== '') { $shortName = str_contains($warehouse, '代发仓') ? ($maps['provider'][$warehouse] ?? $maps['warehouse'][$warehouse] ?? '') : ($maps['warehouse'][$warehouse] ?? ''); } if ($shortName !== '') { $mapped++; } $batch[] = [ 'report_month' => $month . '-01', // 唯一键按 Excel 行号生成,保留原表每一行(含重复行)。 'row_key' => sha1($month . '|' . $rowNumber), 'source_no' => $this->text($cells[0] ?? ''), 'document_no' => $this->text($cells[1] ?? ''), 'created_serial' => $this->text($cells[2] ?? ''), 'supplier' => $this->text($cells[3] ?? ''), 'item_no' => $this->text($cells[4] ?? ''), 'barcode' => $this->text($cells[5] ?? ''), 'spec_name' => $this->text($cells[6] ?? ''), 'quantity' => $this->number($cells[7] ?? ''), 'unit_price' => $this->number($cells[8] ?? ''), 'amount' => $this->number($cells[9] ?? ''), 'estimated_unit_price' => $this->number($cells[10] ?? ''), 'estimated_amount' => $this->number($cells[11] ?? ''), 'supplier_barcode' => $this->text($cells[12] ?? ''), 'remark' => $this->text($cells[13] ?? ''), 'warehouse' => $warehouse !== '' ? $warehouse : null, 'warehouse_short_name' => $shortName !== '' ? $shortName : null, 'created_at_raw' => $this->moment($createdRaw), 'express_name' => $this->text($cells[17] ?? ''), 'express_no' => $this->text($cells[18] ?? ''), 'not_sent' => $this->text($cells[19] ?? ''), 'owner_name' => $this->text($cells[20] ?? ''), 'imported_at' => $now, ]; $imported++; if (count($batch) >= 1000) { DB::connection($connection)->table($table)->insertOrIgnore($batch); $this->info('已写入 ' . $imported . ' 行'); $batch = []; } } if ($batch !== []) { DB::connection($connection)->table($table)->insertOrIgnore($batch); } $this->info(sprintf( '导入完成:%d 行,仓库简称匹配上 %d 行,跳过 %d 行;库中该表共 %d 行', $imported, $mapped, $skipped, DB::connection($connection)->table($table)->count(), )); return self::SUCCESS; } /** * @return array{warehouse:array,provider:array} * 仅保留能唯一匹配的键 */ private function shortNameMap(): array { $rows = InventoryWarehouseNameMapping::withoutGlobalScopes() ->get(['warehouse_provider', 'warehouse', 'warehouse_short_name']); $grouped = []; foreach ($rows as $row) { $warehouse = trim((string) $row->warehouse); if ($warehouse === '') { continue; } $grouped[$warehouse][] = trim((string) $row->warehouse_short_name); } $byWarehouse = []; foreach ($grouped as $warehouse => $names) { $unique = array_values(array_unique($names)); if (count($unique) === 1 && $unique[0] !== '') { $byWarehouse[$warehouse] = $unique[0]; } } $byProvider = []; foreach ($rows as $row) { $provider = trim((string) $row->warehouse_provider); if ($provider === '') { continue; } $byProvider[$provider][] = trim((string) $row->warehouse_short_name); } foreach ($byProvider as $provider => $names) { $unique = array_values(array_unique($names)); if (count($unique) === 1 && $unique[0] !== '') { $byProvider[$provider] = $unique[0]; } } return ['warehouse' => $byWarehouse, 'provider' => $byProvider]; } private function monthOf(string $value): string { $value = trim($value); if ($value === '') { return ''; } foreach (['Y/m/d H:i:s', 'Y-m-d H:i:s', 'Y/m/d', 'Y-m-d'] as $format) { $parsed = \DateTimeImmutable::createFromFormat($format, $value); if ($parsed instanceof \DateTimeImmutable) { return $parsed->format('Y-m'); } } $timestamp = strtotime($value); return $timestamp === false ? '' : date('Y-m', $timestamp); } private function moment(string $value): ?string { $value = trim($value); if ($value === '') { return null; } $timestamp = strtotime($value); return $timestamp === false ? null : date('Y-m-d H:i:s', $timestamp); } private function text(mixed $value): ?string { $text = trim((string) $value); return $text === '' ? null : $text; } private function number(mixed $value): ?float { $text = str_replace(',', '', trim((string) $value)); if ($text === '' || ! is_numeric($text)) { return null; } return (float) $text; } }