| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- <?php
- declare(strict_types=1);
- namespace App\Services\Import;
- use App\Models\CommonCatalogItem;
- use App\Models\File;
- use App\Services\FileService;
- use Illuminate\Support\Facades\Storage;
- use Illuminate\Support\Str;
- use RuntimeException;
- class ImportTechnicalDescriptionsService
- {
- /**
- * @param list<array<string, mixed>> $sourceProducts
- * @return array<string, int|list<string>>
- */
- public function handle(
- array $sourceProducts,
- ?string $imagesDirectory,
- int $userId,
- bool $overwrite = false,
- bool $createMissing = false,
- bool $applyToVariants = false,
- bool $dryRun = false,
- ): array {
- $stats = [
- 'source' => count($sourceProducts),
- 'matched' => 0,
- 'updated' => 0,
- 'created' => 0,
- 'unchanged' => 0,
- 'missing' => 0,
- 'ambiguous' => 0,
- 'images_imported' => 0,
- 'images_missing' => 0,
- 'missing_articles' => [],
- 'ambiguous_articles' => [],
- ];
- foreach ($sourceProducts as $source) {
- $article = trim((string) ($source['article'] ?? ''));
- if ($article === '') {
- continue;
- }
- $items = CommonCatalogItem::query()->where('article', $article)->get();
- if ($items->isEmpty()) {
- if (! $createMissing) {
- $stats['missing']++;
- $stats['missing_articles'][] = $article;
- continue;
- }
- $items = collect([new CommonCatalogItem([
- 'article' => $article,
- 'calculator_name' => $this->nullableString($source['name'] ?? null),
- ])]);
- } elseif ($items->count() > 1 && ! $applyToVariants) {
- $stats['ambiguous']++;
- $stats['ambiguous_articles'][] = $article;
- continue;
- }
- foreach ($items as $item) {
- $stats['matched']++;
- $payload = $this->payload($source);
- $changes = $this->changes($item, $payload, $overwrite);
- $isNew = ! $item->exists;
- $imageSource = $this->imageSource($source, $imagesDirectory);
- $shouldImportImage = $item->image_file_id === null && $imageSource !== null;
- if ($dryRun) {
- if ($isNew) {
- $stats['created']++;
- } elseif ($changes !== [] || $shouldImportImage) {
- $stats['updated']++;
- } else {
- $stats['unchanged']++;
- }
- if ($shouldImportImage) {
- $stats['images_imported']++;
- } elseif ($item->image_file_id === null && ($source['image_path'] ?? '') !== '') {
- $stats['images_missing']++;
- }
- continue;
- }
- if ($changes !== []) {
- $item->fill($changes);
- }
- if ($isNew) {
- $item->save();
- $stats['created']++;
- } elseif ($item->isDirty()) {
- $item->save();
- $stats['updated']++;
- } else {
- $stats['unchanged']++;
- }
- if ($item->image_file_id === null && ($source['image_path'] ?? '') !== '') {
- if ($imageSource === null) {
- $stats['images_missing']++;
- } else {
- $this->importImage($item, $imageSource, $userId);
- $stats['images_imported']++;
- if (! $isNew && $changes === []) {
- $stats['unchanged']--;
- $stats['updated']++;
- }
- }
- }
- }
- }
- $stats['missing_articles'] = array_values(array_unique($stats['missing_articles']));
- $stats['ambiguous_articles'] = array_values(array_unique($stats['ambiguous_articles']));
- return $stats;
- }
- /** @param array<string, mixed> $source */
- private function payload(array $source): array
- {
- return [
- 'series' => $this->nullableString($source['series'] ?? null),
- 'calculator_name' => $this->nullableString($source['name'] ?? null),
- 'print_name' => $this->nullableString($source['name_for_form'] ?? null),
- 'product_group' => $this->nullableString($source['product_group'] ?? null),
- 'characteristics' => $this->nullableString($source['characteristics'] ?? null),
- 'technical_description' => $this->nullableString($source['tech_description'] ?? null),
- 'technical_description_short' => $this->nullableString($source['tech_description_short'] ?? null),
- ];
- }
- private function changes(CommonCatalogItem $item, array $payload, bool $overwrite): array
- {
- return array_filter(
- $payload,
- function (mixed $value, string $field) use ($item, $overwrite): bool {
- if ($value === null) {
- return false;
- }
- return $overwrite || trim((string) $item->getAttribute($field)) === '';
- },
- ARRAY_FILTER_USE_BOTH,
- );
- }
- /** @param array<string, mixed> $source */
- private function imageSource(array $source, ?string $imagesDirectory): ?string
- {
- $imageName = basename(str_replace('\\', '/', (string) ($source['image_path'] ?? '')));
- if ($imageName === '' || $imagesDirectory === null || $imagesDirectory === '') {
- return null;
- }
- $path = rtrim($imagesDirectory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$imageName;
- return is_file($path) && is_readable($path) ? $path : null;
- }
- private function importImage(CommonCatalogItem $item, string $sourcePath, int $userId): void
- {
- $contents = file_get_contents($sourcePath);
- if (! is_string($contents) || $contents === '') {
- throw new RuntimeException("Не удалось прочитать изображение {$sourcePath}.");
- }
- $mimeType = (string) (new \finfo(FILEINFO_MIME_TYPE))->buffer($contents);
- $extension = match ($mimeType) {
- 'image/jpeg' => 'jpg',
- 'image/png' => 'png',
- 'image/webp' => 'webp',
- default => throw new RuntimeException("Неподдерживаемый MIME-тип изображения {$mimeType}."),
- };
- $path = "common_catalog/items/{$item->id}/image/".Str::uuid().".{$extension}";
- if (! Storage::disk('public')->put($path, $contents)) {
- throw new RuntimeException("Не удалось сохранить изображение для артикула {$item->article}.");
- }
- $file = File::query()->create([
- 'link' => url('/storage/'.$path),
- 'path' => $path,
- 'user_id' => $userId,
- 'original_name' => basename($sourcePath),
- 'mime_type' => $mimeType,
- ]);
- app(FileService::class)->ensureThumbnail($file);
- $item->update(['image_file_id' => $file->id]);
- }
- private function nullableString(mixed $value): ?string
- {
- $value = trim((string) $value);
- return $value === '' ? null : $value;
- }
- }
|