path ?? storage_path(self::RELATIVE_PATH); if (! is_file($path)) { return $this->emptySnapshot('missing', '尚未导入本机报价文件。'); } try { $payload = json_decode((string) file_get_contents($path), true, 512, JSON_THROW_ON_ERROR); } catch (JsonException) { return $this->emptySnapshot('invalid', '本机报价快照无法读取,请重新导入源文件。'); } if (! is_array($payload) || ! is_array($payload['sheets'] ?? null)) { return $this->emptySnapshot('invalid', '本机报价快照结构不完整,请重新导入源文件。'); } $quotes = []; foreach (array_values($payload['sheets']) as $position => $sheet) { if (! is_array($sheet) || trim((string) ($sheet['name'] ?? '')) === '') { continue; } $quotes[] = $this->normalizeSheet($sheet, $position); } if (Schema::hasTable('business_courier_quote_versions')) { foreach (CourierQuoteVersion::query()->orderBy('id')->get() as $version) { $quotes[] = $this->normalizeVersion($version); } } return [ 'available' => true, 'state' => 'ready', 'message' => null, 'source_file' => basename(str_replace('\\', '/', (string) ($payload['sourcePath'] ?? '报价单合并.xlsx'))), 'quotes' => $quotes, 'summary' => [ 'quote_count' => count($quotes), 'carrier_count' => count(array_unique(array_column($quotes, 'carrier'))), 'dated_count' => count(array_filter($quotes, fn (array $quote) => $quote['effective_date'] !== null)), 'formula_count' => array_sum(array_column($quotes, 'formula_count')), ], ]; } private function normalizeSheet(array $sheet, int $position): array { $name = trim((string) $sheet['name']); $values = $this->normalizeMatrix($sheet['values'] ?? []); $formulas = $this->normalizeMatrix($sheet['formulas'] ?? []); $columnCount = max(array_map('count', $values ?: [[]])); return [ 'id' => sprintf('QB-%03d', $position + 1), 'sheet_index' => (int) ($sheet['index'] ?? $position), 'sheet_name' => $name, 'company_name' => $this->inferCompanyName($name), 'carrier' => $this->inferCarrier($name), 'quote_type' => $this->inferQuoteType($name), 'effective_date' => $this->extractEffectiveDate($values), 'source_range' => trim((string) ($sheet['address'] ?? '')), 'row_count' => count($values), 'column_count' => $columnCount, 'formula_count' => $this->formulaCount($formulas), 'status' => $this->extractEffectiveDate($values) === null ? '未标执行日期' : '已标执行日期', 'signature_status' => 'Excel 未提供', 'values' => $values, 'formulas' => $formulas, ]; } private function inferCompanyName(string $name): string { $companyName = trim((string) preg_replace('/(?:报价表|报价)$/u', '', $name)); return $companyName !== '' ? $companyName : $name; } private function normalizeVersion(CourierQuoteVersion $version): array { $values = $this->decodeMatrix($version->values_json); $formulas = $this->decodeMatrix($version->formulas_json); $columnCount = max(array_map('count', $values ?: [[]])); return [ 'id' => $version->version_code, 'sheet_index' => null, 'sheet_name' => $version->sheet_name, 'company_name' => $version->company_name, 'carrier' => $version->carrier_name, 'quote_type' => $version->quote_type, 'effective_date' => $version->effective_date?->toDateString(), 'source_range' => $version->source_range ?? '', 'row_count' => count($values), 'column_count' => $columnCount, 'formula_count' => $this->formulaCount($formulas), 'status' => $this->versionLifecycleStatus($version), 'signature_status' => $version->signature_status, 'values' => $values, 'formulas' => $formulas, 'source_change_id' => $version->source_change_id, ]; } private function decodeMatrix(?string $json): array { if ($json === null || trim($json) === '') { return []; } try { return $this->normalizeMatrix(json_decode($json, true, 512, JSON_THROW_ON_ERROR)); } catch (JsonException) { return []; } } private function versionLifecycleStatus(CourierQuoteVersion $version): string { if ($version->signature_status !== '已签署并归档') { return '待生效(待签章)'; } return $version->effective_date?->isFuture() ? '待生效' : '生效中'; } private function normalizeMatrix(mixed $matrix): array { if (! is_array($matrix)) { return []; } return array_map(function ($row) { if (! is_array($row)) { return [$this->normalizeCell($row)]; } return array_map(fn ($cell) => $this->normalizeCell($cell), array_values($row)); }, array_values($matrix)); } private function normalizeCell(mixed $cell): string|int|float|bool|null { if ($cell === null || is_string($cell) || is_int($cell) || is_float($cell) || is_bool($cell)) { return $cell; } return (string) $cell; } private function inferCarrier(string $name): string { foreach ([ '中通快运', '顺心捷达', '安能快运', '京广', 'EMS', '中通', '圆通', '德邦', '申通', '极兔', '安能', '邮政', '韵达', '顺丰', ] as $carrier) { if (str_contains($name, $carrier)) { return $carrier; } } return '其他快递'; } private function inferQuoteType(string $name): string { foreach ([ '其他配件' => '其他配件报价', '配件' => '配件报价', '超长' => '超长报价', '上楼' => '上楼报价', '大件' => '大件报价', '小件' => '小件报价', '其他' => '其他报价', '快运' => '快运报价', ] as $keyword => $type) { if (str_contains($name, $keyword)) { return $type; } } return '标准报价'; } private function extractEffectiveDate(array $values): ?string { foreach ($values as $row) { foreach ($row as $cell) { if (! is_string($cell)) { continue; } if (preg_match('/(20\d{2})[.\/\-年]\s*(\d{1,2})[.\/\-月]\s*(\d{1,2})日?/u', $cell, $match) === 1) { return sprintf('%04d-%02d-%02d', (int) $match[1], (int) $match[2], (int) $match[3]); } } } return null; } private function formulaCount(array $formulas): int { $count = 0; foreach ($formulas as $row) { foreach ($row as $formula) { if (is_string($formula) && trim($formula) !== '') { $count++; } } } return $count; } private function emptySnapshot(string $state, string $message): array { return [ 'available' => false, 'state' => $state, 'message' => $message, 'source_file' => '报价单合并.xlsx', 'quotes' => [], 'summary' => [ 'quote_count' => 0, 'carrier_count' => 0, 'dated_count' => 0, 'formula_count' => 0, ], ]; } }