177 lines
9.6 KiB
PHP
177 lines
9.6 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\Business\CourierQuoteChange;
|
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
|
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
|
|
|
/** 单张变价单导出;以保存的明细为准,不重新读取可变的报价底稿。 */
|
|
final class CourierQuoteChangeExport
|
|
{
|
|
public function build(CourierQuoteChange $change): Spreadsheet
|
|
{
|
|
abort_unless((int) $change->company_id === (int) company_id(), 403);
|
|
abort_if($change->isStockQuote(), 422, '存量报价请在详情页查看原报价资料。');
|
|
$book = new Spreadsheet();
|
|
$book->removeSheetByIndex(0);
|
|
$cells = [];
|
|
if (! $change->hasQuotePayload()) {
|
|
foreach ($change->items()->reorder('source_row')->orderBy('source_column')->cursor() as $item) {
|
|
$cells[] = ['row' => $item->source_row, 'column' => $item->source_column, 'region' => $item->region,
|
|
'label' => $item->weight_band ?: $item->charge_item ?: '价格', 'old' => $item->original_amount,
|
|
'new' => $item->new_amount, 'cell' => $item->source_cell, 'unit' => $item->unit, 'note' => $item->note];
|
|
}
|
|
} else {
|
|
$requotes = app(CourierRequoteService::class);
|
|
$onboarding = $requotes->onboardingForCourier($change->carrier_name);
|
|
abort_if($onboarding === null, 422, '未找到原入驻报价,无法导出新旧对比。');
|
|
$old = $requotes->basePayload($onboarding, $change);
|
|
$diff = $requotes->diff($onboarding, $old, $change->quote_payload);
|
|
foreach ($diff['cells'] as $cell) {
|
|
$cells[] = ['row' => $cell['region'], 'column' => $cell['label'], 'region' => $cell['region'],
|
|
'label' => $cell['label'], 'old' => $cell['old'], 'new' => $cell['new'], 'cell' => '', 'unit' => '元', 'note' => ''];
|
|
}
|
|
}
|
|
|
|
$sheet = $this->sheet($book, $change, '横向变价对比');
|
|
$this->text($sheet, 'A5', '仅列本单已保存的变价项;—表示本单未变更或无对应价格,不代表零价。备注见逐项明细。');
|
|
if (isset($diff)) {
|
|
$this->text($sheet, 'A5', $diff['same_structure']
|
|
? '价格变动见下表;新增或移除地区、规则变更见结构与规则变更及原、新报价全表。'
|
|
: '计费结构已变化,请对照结构与规则变更、原报价全表与新报价全表,不计算不同区间之间的差额。');
|
|
}
|
|
$this->text($sheet, 'A7', '地区 / 规则');
|
|
$sheet->mergeCells('A7:A8');
|
|
$columns = collect($cells)->unique('column')->values();
|
|
foreach ($columns as $index => $column) {
|
|
$start = 2 + $index * 3;
|
|
$this->text($sheet, Coordinate::stringFromColumnIndex($start) . '7', $column['label']);
|
|
$sheet->mergeCells(Coordinate::stringFromColumnIndex($start) . '7:' . Coordinate::stringFromColumnIndex($start + 2) . '7');
|
|
foreach (['原价', '新价', '差额'] as $offset => $label) {
|
|
$this->text($sheet, Coordinate::stringFromColumnIndex($start + $offset) . '8', $label);
|
|
}
|
|
}
|
|
$row = 9;
|
|
foreach (collect($cells)->groupBy('row') as $items) {
|
|
$this->text($sheet, 'A' . $row, $items->first()['region']);
|
|
$byColumn = $items->keyBy('column');
|
|
foreach ($columns as $index => $column) {
|
|
$cell = $byColumn->get($column['column']);
|
|
foreach ([$cell['old'] ?? null, $cell['new'] ?? null, $cell === null ? null : $this->delta($cell)] as $offset => $value) {
|
|
$address = Coordinate::stringFromColumnIndex(2 + $index * 3 + $offset) . $row;
|
|
$this->amount($sheet, $address, $value);
|
|
}
|
|
}
|
|
$row++;
|
|
}
|
|
$this->style($sheet, max(1, 1 + $columns->count() * 3), max(8, $row - 1), 8);
|
|
$sheet->freezePane('B9');
|
|
|
|
$details = $this->sheet($book, $change, '逐项明细');
|
|
foreach (['地区 / 规则', '重量区间 / 计费项', '原价', '新价', '差额', '单位', '备注', '原单元格'] as $index => $label) {
|
|
$this->text($details, Coordinate::stringFromColumnIndex($index + 1) . '7', $label);
|
|
}
|
|
foreach ($cells as $index => $cell) {
|
|
$line = $index + 8;
|
|
foreach (['A' => $cell['region'], 'B' => $cell['label'], 'F' => $cell['unit'], 'G' => $cell['note'], 'H' => $cell['cell']] as $column => $value) {
|
|
$this->text($details, $column . $line, $value);
|
|
}
|
|
foreach (['C' => $cell['old'], 'D' => $cell['new'], 'E' => $this->delta($cell)] as $column => $value) {
|
|
$this->amount($details, $column . $line, $value);
|
|
}
|
|
}
|
|
$this->style($details, 8, max(7, count($cells) + 7), 7);
|
|
$details->getColumnDimension('B')->setWidth(30);
|
|
$details->getColumnDimension('G')->setWidth(48);
|
|
$details->getStyle('G8:G' . max(8, count($cells) + 7))->getAlignment()->setWrapText(true);
|
|
|
|
if (isset($diff)) {
|
|
$rules = $this->sheet($book, $change, '结构与规则变更');
|
|
$rows = [['原计费方式', $diff['old_mode']], ['新计费方式', $diff['new_mode']],
|
|
['新增地区', implode('、', $diff['added']) ?: '无'], ['移除地区', implode('、', $diff['removed']) ?: '无']];
|
|
foreach ($diff['rules'] as $rule) {
|
|
$rows[] = ['原规则', $rule['old']];
|
|
$rows[] = ['新规则', $rule['new']];
|
|
}
|
|
if (! $diff['same_structure']) {
|
|
$rows[] = ['核对说明', '计费结构已变化,请对照原报价全表与新报价全表,不按相同区间计算差额。'];
|
|
}
|
|
foreach ($rows as $index => $values) {
|
|
$this->text($rules, 'A' . ($index + 7), $values[0]);
|
|
$this->text($rules, 'B' . ($index + 7), $values[1]);
|
|
}
|
|
$this->style($rules, 2, count($rows) + 6, 7);
|
|
$rules->getColumnDimension('B')->setWidth(100);
|
|
foreach (['原报价全表' => $old, '新报价全表' => $change->quote_payload] as $title => $payload) {
|
|
$model = $requotes->draftModel($onboarding, $payload);
|
|
$full = $this->sheet($book, $change, $title);
|
|
$headers = ['地区 / 规则', ...array_column($model->quoteColumns(), 'label')];
|
|
foreach ($headers as $index => $label) {
|
|
$this->text($full, Coordinate::stringFromColumnIndex($index + 1) . '7', $label);
|
|
}
|
|
foreach ($model->quoteRowList() as $index => $item) {
|
|
$this->text($full, 'A' . ($index + 8), $item['region']);
|
|
foreach (CourierQuoteStructure::rowValues($item, $model->quoteColumns()) as $column => $value) {
|
|
$this->amount($full, Coordinate::stringFromColumnIndex($column + 2) . ($index + 8), $value);
|
|
}
|
|
}
|
|
$this->style($full, count($headers), max(7, count($model->quoteRowList()) + 7), 7);
|
|
}
|
|
}
|
|
$book->setActiveSheetIndex(0);
|
|
|
|
return $book;
|
|
}
|
|
|
|
private function sheet(Spreadsheet $book, CourierQuoteChange $change, string $title): Worksheet
|
|
{
|
|
$sheet = $book->createSheet()->setTitle($title);
|
|
foreach (['报价变价核对 · ' . $change->number, $change->carrier_name . ' · 原报价 ' . $change->source_quote_id,
|
|
'状态:' . $change->statusLabel() . ';执行期间:' . $change->periodLabel(), '变更说明:' . $change->summary] as $index => $value) {
|
|
$this->text($sheet, 'A' . ($index + 1), $value);
|
|
}
|
|
return $sheet;
|
|
}
|
|
|
|
private function delta(array $cell): ?float
|
|
{
|
|
return $cell['old'] === null || $cell['new'] === null ? null : round((float) $cell['new'] - (float) $cell['old'], 4);
|
|
}
|
|
|
|
private function text(Worksheet $sheet, string $address, mixed $value): void
|
|
{
|
|
$sheet->setCellValueExplicit($address, (string) ($value ?? ''), DataType::TYPE_STRING);
|
|
}
|
|
|
|
private function amount(Worksheet $sheet, string $address, mixed $value): void
|
|
{
|
|
if ($value === null) {
|
|
$this->text($sheet, $address, '—');
|
|
return;
|
|
}
|
|
$sheet->setCellValueExplicit($address, round((float) $value, 4), DataType::TYPE_NUMERIC);
|
|
$sheet->getStyle($address)->getNumberFormat()->setFormatCode('0.0000');
|
|
}
|
|
|
|
private function style(Worksheet $sheet, int $columns, int $lastRow, int $headerRow): void
|
|
{
|
|
$last = Coordinate::stringFromColumnIndex($columns);
|
|
$sheet->getStyle('A1:' . $last . $lastRow)->getFont()->setName('Microsoft YaHei')->setSize(10);
|
|
$sheet->getStyle('A7:' . $last . $headerRow)->applyFromArray([
|
|
'font' => ['bold' => true, 'color' => ['rgb' => '513E21']],
|
|
'fill' => ['fillType' => 'solid', 'startColor' => ['rgb' => 'F7F0DE']],
|
|
'alignment' => ['wrapText' => true, 'vertical' => 'center'],
|
|
]);
|
|
foreach (range(1, $columns) as $column) {
|
|
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex($column))->setWidth($column === 1 ? 22 : 16);
|
|
}
|
|
$sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
|
|
$sheet->freezePane('B' . ($headerRow + 1));
|
|
$sheet->setAutoFilter('A' . $headerRow . ':' . $last . $lastRow);
|
|
$sheet->getPageSetup()->setOrientation('landscape')->setFitToWidth(1)->setFitToHeight(0);
|
|
}
|
|
}
|