CaiWuXiTong/app/Business/BusinessTableImporter.php

189 lines
6.2 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 App\Business;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
class BusinessTableImporter
{
public function import(
UploadedFile $file,
TableDescriptor $descriptor,
string $section,
string $page,
?string $tab = null,
): array {
if ($section === 'fund-management' && $page === 'fund-forecast') {
return app(FundForecastWorkbookImporter::class)->import(
$file,
(int) (company_id() ?: 1),
);
}
$spreadsheet = IOFactory::load($file->getRealPath());
[$rows, $headerRow, $columnMap] = $this->bestMatchingSheet($spreadsheet, $descriptor);
if ($rows === null || $columnMap === []) {
throw ValidationException::withMessages([
'import' => '未找到可识别的表头,请使用当前页面显示的中文表头。',
]);
}
$created = 0;
$updated = 0;
$model = $descriptor->model;
$identityFields = TableRegistry::importIdentityFields($section, $page);
DB::transaction(function () use (
$rows,
$headerRow,
$columnMap,
$descriptor,
$section,
$page,
$tab,
$model,
$identityFields,
&$created,
&$updated,
) {
foreach (array_slice($rows, $headerRow + 1) as $offset => $row) {
$data = [];
foreach ($columnMap as $columnIndex => $field) {
$data[$field['name']] = $this->normalizeValue(
$row[$columnIndex] ?? null,
$field['type'],
$headerRow + $offset + 2,
$field['label'],
);
}
if (collect($data)->every(fn ($value) => $value === null || $value === '')) {
continue;
}
if ($descriptor->tabFilterColumn && $tab) {
$tabValue = TableRegistry::tabValue($section, $page, $tab);
if ($tabValue !== null) {
$data[$descriptor->tabFilterColumn] = $tabValue;
}
}
foreach (TableRegistry::fixedFilters($section, $page) as $column => $value) {
$data[$column] = $value;
}
$data['company_id'] = company_id() ?: 1;
$identity = collect($identityFields)
->filter(fn ($field) => array_key_exists($field, $data) && $data[$field] !== null && $data[$field] !== '')
->mapWithKeys(fn ($field) => [$field => $data[$field]])
->all();
$record = count($identity) === count($identityFields) && $identity !== []
? $model::query()->where($identity)->first()
: null;
if ($record) {
$record->update($data);
$updated++;
} else {
$model::create($data);
$created++;
}
}
});
if ($created + $updated === 0) {
throw ValidationException::withMessages([
'import' => '表格中没有可导入的数据行。',
]);
}
return compact('created', 'updated');
}
private function bestMatchingSheet($spreadsheet, TableDescriptor $descriptor): array
{
$fieldLookup = [];
foreach ($descriptor->fields as $field) {
$fieldLookup[$this->normalizeHeader($field['name'])] = $field;
$fieldLookup[$this->normalizeHeader($field['label'])] = $field;
}
$best = [null, null, []];
$bestScore = 0;
foreach ($spreadsheet->getWorksheetIterator() as $worksheet) {
$rows = $worksheet->toArray(null, true, false, false);
foreach (array_slice($rows, 0, 20, true) as $rowIndex => $row) {
$columnMap = [];
foreach ($row as $columnIndex => $heading) {
$key = $this->normalizeHeader($heading);
if ($key !== '' && isset($fieldLookup[$key])) {
$columnMap[$columnIndex] = $fieldLookup[$key];
}
}
if (count($columnMap) > $bestScore) {
$best = [$rows, $rowIndex, $columnMap];
$bestScore = count($columnMap);
}
}
}
return $best;
}
private function normalizeHeader($value): string
{
return mb_strtolower(preg_replace('/[\s\x{FEFF}_\-()()]+/u', '', trim((string) $value)));
}
private function normalizeValue($value, string $type, int $rowNumber, string $label)
{
if ($value === null || trim((string) $value) === '') {
return null;
}
if ($type === 'date') {
try {
return is_numeric($value)
? ExcelDate::excelToDateTimeObject($value)->format('Y-m-d')
: \Illuminate\Support\Carbon::parse($value)->format('Y-m-d');
} catch (\Throwable $e) {
throw ValidationException::withMessages([
'import' => "第 {$rowNumber} 行“{$label}”不是有效日期。",
]);
}
}
if (in_array($type, ['number', 'currency'], true)) {
$number = str_replace([',', ',', '¥', '¥'], '', (string) $value);
if (! is_numeric($number)) {
throw ValidationException::withMessages([
'import' => "第 {$rowNumber} 行“{$label}”不是有效数字。",
]);
}
return (float) $number;
}
if ($type === 'boolean') {
return in_array(mb_strtolower(trim((string) $value)), ['1', '是', 'true', 'yes', 'y'], true) ? 1 : 0;
}
return trim((string) $value);
}
}