- EquipmentPhotoService: GCS 기반 사진 업로드/삭제/조회 (최대 10장) - EquipmentImportService: 엑셀 파싱 → 설비 일괄 등록 (한글 헤더 자동 매핑) - API: 사진 업로드/목록/삭제, Import 미리보기/실행 엔드포인트 - 뷰: create/edit에 드래그앤드롭 사진 업로드, show에 갤러리 표시 - import.blade.php: 3단계 Import UI (파일선택 → 미리보기 → 결과) - phpoffice/phpspreadsheet 패키지 추가
135 lines
4.1 KiB
PHP
135 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Boards\File;
|
|
use App\Models\Equipment\Equipment;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class EquipmentPhotoService
|
|
{
|
|
private const MAX_PHOTOS = 10;
|
|
|
|
public function __construct(
|
|
private readonly GoogleCloudService $googleCloudService
|
|
) {}
|
|
|
|
/**
|
|
* 설비 사진 업로드 (멀티)
|
|
*
|
|
* @param array<UploadedFile> $files
|
|
* @return array ['uploaded' => int, 'errors' => array]
|
|
*/
|
|
public function uploadPhotos(Equipment $equipment, array $files): array
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
$currentCount = $equipment->photos()->count();
|
|
$uploaded = 0;
|
|
$errors = [];
|
|
|
|
foreach ($files as $index => $file) {
|
|
if ($currentCount + $uploaded >= self::MAX_PHOTOS) {
|
|
$errors[] = "최대 ".self::MAX_PHOTOS."장까지 업로드 가능합니다.";
|
|
break;
|
|
}
|
|
|
|
$extension = $file->getClientOriginalExtension();
|
|
$timestamp = now()->format('Ymd_His');
|
|
$objectName = "equipment-photos/{$tenantId}/{$equipment->id}/{$timestamp}_{$index}.{$extension}";
|
|
|
|
$tempPath = $file->getRealPath();
|
|
$result = $this->googleCloudService->uploadToStorage($tempPath, $objectName);
|
|
|
|
if (! $result) {
|
|
$errors[] = "파일 '{$file->getClientOriginalName()}' 업로드 실패";
|
|
Log::error('EquipmentPhoto: GCS 업로드 실패', [
|
|
'equipment_id' => $equipment->id,
|
|
'file' => $file->getClientOriginalName(),
|
|
]);
|
|
|
|
continue;
|
|
}
|
|
|
|
File::create([
|
|
'tenant_id' => $tenantId,
|
|
'document_id' => $equipment->id,
|
|
'document_type' => 'equipment',
|
|
'file_path' => $objectName,
|
|
'original_name' => $file->getClientOriginalName(),
|
|
'display_name' => $file->getClientOriginalName(),
|
|
'file_size' => $result['size'],
|
|
'mime_type' => $file->getMimeType(),
|
|
'file_type' => 'image',
|
|
'gcs_object_name' => $objectName,
|
|
'gcs_uri' => $result['uri'],
|
|
'uploaded_by' => Auth::id(),
|
|
'is_temp' => false,
|
|
]);
|
|
|
|
$uploaded++;
|
|
}
|
|
|
|
return ['uploaded' => $uploaded, 'errors' => $errors];
|
|
}
|
|
|
|
/**
|
|
* 설비 사진 삭제
|
|
*/
|
|
public function deletePhoto(Equipment $equipment, int $fileId): bool
|
|
{
|
|
$file = $equipment->photos()->where('id', $fileId)->first();
|
|
|
|
if (! $file) {
|
|
return false;
|
|
}
|
|
|
|
if ($file->gcs_object_name) {
|
|
$this->googleCloudService->deleteFromStorage($file->gcs_object_name);
|
|
}
|
|
|
|
$file->forceDelete();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 설비 사진 목록 (Signed URL 포함)
|
|
*/
|
|
public function getPhotoUrls(Equipment $equipment): array
|
|
{
|
|
$photos = $equipment->photos()->get();
|
|
$bucket = config('services.google.storage_bucket');
|
|
|
|
return $photos->map(function ($photo) use ($bucket) {
|
|
$url = null;
|
|
if ($photo->gcs_object_name && $bucket) {
|
|
$url = "https://storage.googleapis.com/{$bucket}/{$photo->gcs_object_name}";
|
|
}
|
|
|
|
return [
|
|
'id' => $photo->id,
|
|
'original_name' => $photo->original_name,
|
|
'file_size' => $photo->file_size,
|
|
'mime_type' => $photo->mime_type,
|
|
'url' => $url,
|
|
'created_at' => $photo->created_at?->format('Y-m-d H:i'),
|
|
];
|
|
})->toArray();
|
|
}
|
|
|
|
/**
|
|
* 설비의 모든 사진 삭제 (설비 삭제 시)
|
|
*/
|
|
public function deleteAllPhotos(Equipment $equipment): void
|
|
{
|
|
foreach ($equipment->photos as $photo) {
|
|
if ($photo->gcs_object_name) {
|
|
$this->googleCloudService->deleteFromStorage($photo->gcs_object_name);
|
|
}
|
|
$photo->forceDelete();
|
|
}
|
|
}
|
|
}
|