254 lines
8.3 KiB
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,
|
|
],
|
|
];
|
|
}
|
|
}
|