Files
sam-manage/app/Services/ConstructionSitePhotoService.php
김보곤 9d314a62fa feat:공사현장 사진대지 멀티행(N행) 사진 지원
- ConstructionSitePhotoRow 모델 추가
- 부모 모델에서 사진 컬럼 제거, rows() 관계 추가
- 서비스/컨트롤러에 행 추가/삭제 기능 추가
- 라우트를 행 기반 URL 구조로 변경
- 프론트엔드 멀티행 UI 전면 개편

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:04:09 +09:00

183 lines
5.5 KiB
PHP

<?php
namespace App\Services;
use App\Helpers\AiTokenHelper;
use App\Models\Juil\ConstructionSitePhoto;
use App\Models\Juil\ConstructionSitePhotoRow;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class ConstructionSitePhotoService
{
public function __construct(
private readonly GoogleCloudService $googleCloudService
) {}
public function getList(array $params): LengthAwarePaginator
{
$query = ConstructionSitePhoto::with(['user', 'rows'])
->orderBy('work_date', 'desc')
->orderBy('id', 'desc');
if (!empty($params['search'])) {
$search = $params['search'];
$query->where(function ($q) use ($search) {
$q->where('site_name', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
if (!empty($params['date_from'])) {
$query->where('work_date', '>=', $params['date_from']);
}
if (!empty($params['date_to'])) {
$query->where('work_date', '<=', $params['date_to']);
}
$perPage = (int) ($params['per_page'] ?? 12);
return $query->paginate($perPage);
}
public function create(array $data): ConstructionSitePhoto
{
$photo = ConstructionSitePhoto::create([
'tenant_id' => session('selected_tenant_id'),
'user_id' => Auth::id(),
'site_name' => $data['site_name'],
'work_date' => $data['work_date'],
'description' => $data['description'] ?? null,
]);
// 기본 빈 행 1개 자동 생성
$photo->rows()->create(['sort_order' => 0]);
return $photo->load('rows');
}
public function uploadPhoto(ConstructionSitePhotoRow $row, $file, string $type): bool
{
if (!in_array($type, ['before', 'during', 'after'])) {
return false;
}
$photo = $row->constructionSitePhoto;
$extension = $file->getClientOriginalExtension();
$timestamp = now()->format('Ymd_His');
$objectName = "construction-site-photos/{$photo->tenant_id}/{$photo->id}/{$row->id}_{$timestamp}_{$type}.{$extension}";
// 기존 사진이 있으면 GCS에서 삭제
$oldPath = $row->{$type . '_photo_path'};
if ($oldPath) {
$this->googleCloudService->deleteFromStorage($oldPath);
}
// 임시 파일로 저장 후 GCS 업로드
$tempPath = $file->getRealPath();
$result = $this->googleCloudService->uploadToStorage($tempPath, $objectName);
if (!$result) {
Log::error('ConstructionSitePhoto: GCS 업로드 실패', [
'photo_id' => $photo->id,
'row_id' => $row->id,
'type' => $type,
]);
return false;
}
$row->update([
$type . '_photo_path' => $objectName,
$type . '_photo_gcs_uri' => $result['uri'],
$type . '_photo_size' => $result['size'],
]);
AiTokenHelper::saveGcsStorageUsage('공사현장사진대지-GCS저장', $result['size']);
return true;
}
public function update(ConstructionSitePhoto $photo, array $data): ConstructionSitePhoto
{
$photo->update([
'site_name' => $data['site_name'],
'work_date' => $data['work_date'],
'description' => $data['description'] ?? null,
]);
return $photo->fresh()->load('rows');
}
public function delete(ConstructionSitePhoto $photo): bool
{
// rows 순회하여 모든 GCS 파일 삭제
foreach ($photo->rows as $row) {
foreach (['before', 'during', 'after'] as $type) {
$path = $row->{$type . '_photo_path'};
if ($path) {
$this->googleCloudService->deleteFromStorage($path);
}
}
}
return $photo->delete();
}
public function deletePhotoByType(ConstructionSitePhotoRow $row, string $type): bool
{
if (!in_array($type, ['before', 'during', 'after'])) {
return false;
}
$path = $row->{$type . '_photo_path'};
if ($path) {
$this->googleCloudService->deleteFromStorage($path);
}
$row->update([
$type . '_photo_path' => null,
$type . '_photo_gcs_uri' => null,
$type . '_photo_size' => null,
]);
return true;
}
public function addRow(ConstructionSitePhoto $photo): ConstructionSitePhotoRow
{
$nextOrder = ($photo->rows()->max('sort_order') ?? -1) + 1;
return $photo->rows()->create(['sort_order' => $nextOrder]);
}
public function deleteRow(ConstructionSitePhotoRow $row): bool
{
// 행의 GCS 파일 삭제
foreach (['before', 'during', 'after'] as $type) {
$path = $row->{$type . '_photo_path'};
if ($path) {
$this->googleCloudService->deleteFromStorage($path);
}
}
$photoId = $row->construction_site_photo_id;
$row->delete();
// 나머지 행 순서 재정렬
$remainingRows = ConstructionSitePhotoRow::where('construction_site_photo_id', $photoId)
->orderBy('sort_order')
->get();
foreach ($remainingRows as $i => $r) {
if ($r->sort_order !== $i) {
$r->update(['sort_order' => $i]);
}
}
return true;
}
}