CaiWuXiTong/modules/EcommerceFinance/Services/SupplierPayableWorkbookImporter.php

130 lines
5.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. 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 Throwable;
class SupplierPayableWorkbookImporter
{
public function __construct(
private readonly SupplierPayableMaintenanceRegistry $registry,
private readonly SupplierPayableOverrideStore $overrides,
) {
}
/** @return array{created:int,updated:int,rows:int} */
public function import(string $dataset, UploadedFile $file, int $companyId, ?int $userId): array
{
$definitions = $this->registry->fields($dataset);
$lookup = ['记录标识' => '_action_key', 'recordkey' => '_action_key', '_action_key' => '_action_key'];
foreach ($definitions as $field => $definition) {
$lookup[$this->header($field)] = $field;
$lookup[$this->header($definition['label'])] = $field;
}
try {
$readerType = match (mb_strtolower($file->getClientOriginalExtension())) {
'xlsx' => 'Xlsx',
'xls' => 'Xls',
'csv' => 'Csv',
default => throw new \RuntimeException('Unsupported workbook format.'),
};
$spreadsheet = IOFactory::createReader($readerType)->load($file->getRealPath());
[$rows, $headerRow, $columns] = $this->bestSheet($spreadsheet, $lookup);
} catch (Throwable) {
throw ValidationException::withMessages([
'import' => '无法读取导入文件,请确认文件未损坏且格式为 xlsx、xls 或 csv。',
]);
} finally {
if (isset($spreadsheet)) {
$spreadsheet->disconnectWorksheets();
}
}
if ($rows === null || ! in_array('_action_key', $columns, true)) {
throw ValidationException::withMessages([
'import' => '未找到“记录标识”列,请先导出当前数据,再使用导出的表格修改。',
]);
}
$records = [];
foreach (array_slice($rows, $headerRow + 1, null, true) as $rowIndex => $row) {
$line = $rowIndex + 1;
$key = '';
$rawValues = [];
foreach ($columns as $columnIndex => $field) {
if ($field === '_action_key') {
$key = trim((string) ($row[$columnIndex] ?? ''));
} else {
$rawValues[$field] = $row[$columnIndex] ?? null;
}
}
if ($key === '' && collect($rawValues)->every(static fn (mixed $value): bool => trim((string) $value) === '')) {
continue;
}
if (! str_starts_with($key, 'id:') && ! preg_match('/^hash:[a-f0-9]{64}$/', $key)) {
throw ValidationException::withMessages(['import' => "第 {$line} 行“记录标识”无效,请勿修改该列。"]);
}
if (isset($records[$key])) {
throw ValidationException::withMessages(['import' => "第 {$line} 行“记录标识”重复。"]);
}
try {
$records[$key] = $this->registry->normalize($dataset, $rawValues);
} catch (ValidationException $exception) {
throw ValidationException::withMessages([
'import' => "第 {$line} 行:" . collect($exception->errors())->flatten()->first(),
]);
}
if (count($records) > 10000) {
throw ValidationException::withMessages(['import' => '单次最多导入 10,000 条记录,请拆分文件后重试。']);
}
}
if ($records === []) {
throw ValidationException::withMessages(['import' => '表格中没有可导入的数据行。']);
}
$counts = ['created' => 0, 'updated' => 0];
$this->overrides->transaction(function () use ($records, $dataset, $companyId, $userId, &$counts): void {
foreach ($records as $key => $values) {
$result = $this->overrides->save($dataset, $key, $values, $companyId, $userId, 'import');
$counts['created'] += $result['created'];
$counts['updated'] += $result['updated'];
}
});
return $counts + ['rows' => count($records)];
}
private function bestSheet(mixed $spreadsheet, array $lookup): array
{
$best = [null, null, []];
$score = 0;
foreach ($spreadsheet->getWorksheetIterator() as $worksheet) {
$rows = $worksheet->toArray(null, false, false, false);
foreach (array_slice($rows, 0, 20, true) as $rowIndex => $row) {
$columns = [];
foreach ($row as $columnIndex => $heading) {
$key = $this->header($heading);
if ($key !== '' && isset($lookup[$key])) {
$columns[$columnIndex] = $lookup[$key];
}
}
if (count($columns) > $score) {
$best = [$rows, $rowIndex, $columns];
$score = count($columns);
}
}
}
return $best;
}
private function header(mixed $value): string
{
return mb_strtolower(preg_replace('/[\s\x{FEFF}_\-()()]+/u', '', trim((string) $value)));
}
}