CaiWuXiTong/app/Support/QuoteManagementSnapshot.php

254 lines
8.3 KiB
PHP

<?php
namespace App\Support;
use App\Models\Business\CourierQuoteVersion;
use Illuminate\Support\Facades\Schema;
use JsonException;
final class QuoteManagementSnapshot
{
public const RELATIVE_PATH = 'app/private/quote-management/quotes.json';
public function __construct(private readonly ?string $path = null)
{
}
public function load(): array
{
$path = $this->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,
],
];
}
}