CaiWuXiTong/modules/EcommerceFinance/Services/SupplierFreeShippingWorkbook.php
renshen 176357871b feat: 供应商管理新增是否包邮厂家
在供应商名称后新增「是否包邮厂家」,支持行内编辑、模板导入和导出;空白按不是包邮厂家处理。
2026-10-21 11:20:58 +08:00

201 lines
8.6 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Modules\EcommerceFinance\Services;
use Illuminate\Http\UploadedFile;
use Illuminate\Validation\ValidationException;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Throwable;
/** 读取和生成「是否包邮厂家」导入文件。 */
class SupplierFreeShippingWorkbook
{
public const SUPPLIER_CODE_HEADER = '供应商代码';
public const FIELD_HEADER = '是否包邮厂家';
/**
* 返回供应商代码 => 是否包邮厂家。空白或“-”表示否。
*
* @return array<string,bool>
*/
public function read(UploadedFile $file): array
{
try {
$reader = IOFactory::createReader(match (mb_strtolower($file->getClientOriginalExtension())) {
'xlsx' => 'Xlsx',
'xls' => 'Xls',
'csv' => 'Csv',
default => throw new \RuntimeException('Unsupported workbook format.'),
});
$book = $reader->load($file->getRealPath());
} catch (Throwable) {
throw ValidationException::withMessages([
'import' => '无法读取导入文件,请确认文件未损坏且格式为 xlsx、xls 或 csv。',
]);
}
try {
[$sheet, $headerRow, $codeColumn, $fieldColumn] = $this->locateColumns($book);
$highestRow = $sheet->getHighestDataRow();
if ($highestRow > 5001) {
throw ValidationException::withMessages(['import' => '单次最多导入 5,000 条供应商记录,请拆分文件后重试。']);
}
$records = [];
$lineByCode = [];
for ($row = $headerRow + 1; $row <= $highestRow; $row++) {
$code = $this->text($sheet, $codeColumn, $row);
if (str_starts_with($code, "'")) {
$code = substr($code, 1);
}
$field = $this->text($sheet, $fieldColumn, $row);
if ($code === '' && $field === '') {
continue;
}
if ($code === '') {
throw ValidationException::withMessages(['import' => "第 {$row} 行供应商代码为空。"]);
}
if (mb_strlen($code) > 191) {
throw ValidationException::withMessages(['import' => "第 {$row} 行供应商代码超过 191 个字符。"]);
}
$codeKey = mb_strtolower($code);
if (isset($lineByCode[$codeKey])) {
throw ValidationException::withMessages([
'import' => "第 {$row} 行供应商代码“{$code}”与第 {$lineByCode[$codeKey]} 行重复。",
]);
}
$records[$code] = $this->parseFlag($field, $row);
$lineByCode[$codeKey] = $row;
}
if ($records === []) {
throw ValidationException::withMessages(['import' => '表格中没有可导入的供应商记录。']);
}
return $records;
} finally {
$book->disconnectWorksheets();
}
}
public function template(): StreamedResponse
{
return response()->streamDownload(function (): void {
$book = new Spreadsheet();
$sheet = $book->getActiveSheet();
$sheet->setTitle('是否包邮厂家');
$sheet->fromArray([self::SUPPLIER_CODE_HEADER, '供应商名称', self::FIELD_HEADER], null, 'A1');
$sheet->getStyle('A1:C1')->applyFromArray([
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '856632']],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D8D2C7']]],
]);
$sheet->getRowDimension(1)->setRowHeight(24);
$sheet->freezePane('A2');
$sheet->setAutoFilter('A1:C1');
foreach (['A' => 22, 'B' => 34, 'C' => 20] as $column => $width) {
$sheet->getColumnDimension($column)->setWidth($width);
}
$sheet->getStyle('A2:A5001')->getNumberFormat()->setFormatCode('@');
$instructions = $book->createSheet();
$instructions->setTitle('填写说明');
$instructions->fromArray([
['项目', '说明'],
[self::SUPPLIER_CODE_HEADER, '必填。请填写供应商管理页面中的供应商代码,按代码精确关联。'],
[self::FIELD_HEADER, '填写“包邮”表示是包邮厂家;空白或“-”表示不是包邮厂家。'],
['兼容导出表', '也可以使用本页面导出的文件,在此列补充后直接导入。'],
['覆盖规则', '同一供应商再次导入会更新该字段;其他列不会修改。'],
], null, 'A1');
$instructions->getStyle('A1:B1')->applyFromArray([
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '856632']],
]);
$instructions->getColumnDimension('A')->setWidth(22);
$instructions->getColumnDimension('B')->setWidth(76);
$instructions->getStyle('A1:B5')->getAlignment()->setVertical(Alignment::VERTICAL_CENTER)->setWrapText(true);
foreach (range(1, 5) as $row) {
$instructions->getRowDimension($row)->setRowHeight($row === 1 ? 24 : 34);
}
$book->setActiveSheetIndex(0);
(new Xlsx($book))->save('php://output');
$book->disconnectWorksheets();
}, '供应商是否包邮厂家_导入模板_' . now()->format('Ymd') . '.xlsx', [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Cache-Control' => 'private, no-store',
]);
}
/** @return array{0:Worksheet,1:int,2:int,3:int} */
private function locateColumns(Spreadsheet $book): array
{
$best = null;
foreach ($book->getWorksheetIterator() as $sheet) {
$rows = $sheet->toArray(null, false, false, false);
foreach (array_slice($rows, 0, 20, true) as $rowIndex => $row) {
$headers = [];
foreach ($row as $columnIndex => $value) {
$header = $this->header($value);
if ($header !== '') {
$headers[$header] = $columnIndex + 1;
}
}
if (isset($headers[self::SUPPLIER_CODE_HEADER], $headers[self::FIELD_HEADER])) {
$candidate = [$sheet, $rowIndex + 1, $headers[self::SUPPLIER_CODE_HEADER], $headers[self::FIELD_HEADER]];
$best = $candidate;
break 2;
}
}
}
if ($best === null) {
throw ValidationException::withMessages([
'import' => '缺少表头:需要“供应商代码”和“是否包邮厂家”两列。',
]);
}
return $best;
}
private function parseFlag(string $value, int $row): bool
{
$normalized = mb_strtolower(preg_replace('/[\s\x{FEFF}]+/u', '', $value));
if (in_array($normalized, ['', '-', '否', '不是', '0', 'false', 'no'], true)) {
return false;
}
if (in_array($normalized, ['包邮', '是', '是包邮厂家', '1', 'true', 'yes'], true)) {
return true;
}
throw ValidationException::withMessages([
'import' => "第 {$row} 行“是否包邮厂家”只能填写“包邮”,或留空表示否。",
]);
}
private function text(Worksheet $sheet, int $column, int $row): string
{
$cell = $sheet->getCell([$column, $row]);
if ($cell->getDataType() === 'f') {
throw ValidationException::withMessages(['import' => "第 {$row} 行含公式,请填写实际值。"]);
}
return trim((string) $cell->getFormattedValue());
}
private function header(mixed $value): string
{
return str_replace([' ', ' ', "\n", "\r", "\t", "\x{FEFF}"], '', trim((string) $value));
}
}