base commit
This commit is contained in:
219
app/Services/Import/ImportService.php
Normal file
219
app/Services/Import/ImportService.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Import;
|
||||
|
||||
use App\DTOs\Import\BonusRowDto;
|
||||
use App\DTOs\Import\EmployeeRowDto;
|
||||
use App\DTOs\Import\ImportResultDto;
|
||||
use App\DTOs\Import\ReportRowDto;
|
||||
use App\DTOs\Import\VacationRowDto;
|
||||
use App\Enums\ImportType;
|
||||
use App\Models\Bonus;
|
||||
use App\Models\DisciplinaryReport;
|
||||
use App\Models\Employee;
|
||||
use App\Models\Vacation;
|
||||
use App\Services\Employee\EmployeeService;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
class ImportService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EmployeeImportTransformer $employeeTransformer,
|
||||
private readonly VacationImportTransformer $vacationTransformer,
|
||||
private readonly BonusImportTransformer $bonusTransformer,
|
||||
private readonly ReportImportTransformer $reportTransformer,
|
||||
private readonly EmployeeService $employeeService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function readHeaders(string $path): array
|
||||
{
|
||||
$sheet = IOFactory::load($path)->getActiveSheet();
|
||||
$headers = [];
|
||||
|
||||
foreach ($sheet->getRowIterator(1, 1) as $row) {
|
||||
foreach ($row->getCellIterator() as $cell) {
|
||||
$value = trim((string) $cell->getValue());
|
||||
|
||||
if ($value !== '') {
|
||||
$headers[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function readRows(string $path, ?int $limit = null): array
|
||||
{
|
||||
$sheet = IOFactory::load($path)->getActiveSheet();
|
||||
$headers = $this->readHeaders($path);
|
||||
$rows = [];
|
||||
$highestRow = $sheet->getHighestDataRow();
|
||||
|
||||
for ($rowIndex = 2; $rowIndex <= $highestRow; $rowIndex++) {
|
||||
$rowData = [];
|
||||
$isEmpty = true;
|
||||
|
||||
foreach ($headers as $columnIndex => $header) {
|
||||
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($columnIndex + 1);
|
||||
$value = $sheet->getCell($columnLetter . $rowIndex)->getFormattedValue();
|
||||
$rowData[$header] = is_string($value) ? trim($value) : $value;
|
||||
|
||||
if ($rowData[$header] !== null && $rowData[$header] !== '') {
|
||||
$isEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $isEmpty) {
|
||||
$rows[] = $rowData;
|
||||
}
|
||||
|
||||
if ($limit !== null && count($rows) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string|null> $columnMapping
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function mapRows(array $rows, array $columnMapping): array
|
||||
{
|
||||
return array_map(function (array $row) use ($columnMapping): array {
|
||||
$mapped = [];
|
||||
|
||||
foreach ($columnMapping as $field => $header) {
|
||||
if ($header === null || $header === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapped[$field] = $row[$header] ?? null;
|
||||
}
|
||||
|
||||
return $mapped;
|
||||
}, $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $mappedRows
|
||||
* @return array<int, array{row: int, errors: array<int, string>}>
|
||||
*/
|
||||
public function validatePreview(ImportType $type, array $mappedRows, int $limit = 10): array
|
||||
{
|
||||
$previewRows = array_slice($mappedRows, 0, $limit);
|
||||
$results = [];
|
||||
|
||||
foreach ($previewRows as $index => $row) {
|
||||
$results[] = [
|
||||
'row' => $index + 2,
|
||||
'errors' => $this->validateRow($type, $row),
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $mappedRows
|
||||
*/
|
||||
public function import(ImportType $type, array $mappedRows, ?int $userId = null): ImportResultDto
|
||||
{
|
||||
$imported = 0;
|
||||
$skipped = 0;
|
||||
$failed = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($mappedRows as $index => $row) {
|
||||
$rowNumber = $index + 2;
|
||||
$validationErrors = $this->validateRow($type, $row);
|
||||
|
||||
if ($validationErrors !== []) {
|
||||
$failed++;
|
||||
$errors[] = "Row {$rowNumber}: " . implode(' ', $validationErrors);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isDuplicate($type, $row)) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->persistRow($type, $row, $userId);
|
||||
$imported++;
|
||||
} catch (\Throwable $exception) {
|
||||
$failed++;
|
||||
$errors[] = "Row {$rowNumber}: {$exception->getMessage()}";
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportResultDto(
|
||||
total: count($mappedRows),
|
||||
imported: $imported,
|
||||
skipped: $skipped,
|
||||
failed: $failed,
|
||||
errors: array_slice($errors, 0, 50),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function validateRow(ImportType $type, array $row): array
|
||||
{
|
||||
return match ($type) {
|
||||
ImportType::Employees => $this->employeeTransformer->validate(EmployeeRowDto::fromMappedRow($row)),
|
||||
ImportType::Vacations => $this->vacationTransformer->validate(VacationRowDto::fromMappedRow($row)),
|
||||
ImportType::Bonuses => $this->bonusTransformer->validate(BonusRowDto::fromMappedRow($row)),
|
||||
ImportType::Reports => $this->reportTransformer->validate(ReportRowDto::fromMappedRow($row)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*/
|
||||
private function isDuplicate(ImportType $type, array $row): bool
|
||||
{
|
||||
return match ($type) {
|
||||
ImportType::Employees => $this->employeeTransformer->isDuplicate(EmployeeRowDto::fromMappedRow($row)),
|
||||
ImportType::Vacations => $this->vacationTransformer->isDuplicate(VacationRowDto::fromMappedRow($row)),
|
||||
ImportType::Bonuses => $this->bonusTransformer->isDuplicate(BonusRowDto::fromMappedRow($row)),
|
||||
ImportType::Reports => $this->reportTransformer->isDuplicate(ReportRowDto::fromMappedRow($row)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*/
|
||||
private function persistRow(ImportType $type, array $row, ?int $userId): void
|
||||
{
|
||||
match ($type) {
|
||||
ImportType::Employees => $this->employeeService->create(
|
||||
$this->employeeTransformer->toAttributes(EmployeeRowDto::fromMappedRow($row)),
|
||||
),
|
||||
ImportType::Vacations => Vacation::create(
|
||||
$this->vacationTransformer->toAttributes(VacationRowDto::fromMappedRow($row)),
|
||||
),
|
||||
ImportType::Bonuses => Bonus::create(
|
||||
$this->bonusTransformer->toAttributes(BonusRowDto::fromMappedRow($row)),
|
||||
),
|
||||
ImportType::Reports => DisciplinaryReport::create(
|
||||
$this->reportTransformer->toAttributes(ReportRowDto::fromMappedRow($row), $userId),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user