diff --git a/app/Http/Controllers/Api/V1/ItemsFileController.php b/app/Http/Controllers/Api/V1/ItemsFileController.php index d16d9ca..a9b3f62 100644 --- a/app/Http/Controllers/Api/V1/ItemsFileController.php +++ b/app/Http/Controllers/Api/V1/ItemsFileController.php @@ -51,13 +51,13 @@ public function index(int $id, Request $request) // 특정 field_key만 조회 if ($fieldKey) { - $query->where('file_type', $fieldKey); + $query->where('field_key', $fieldKey); } $files = $query->orderBy('created_at', 'desc')->get(); // field_key별 그룹핑 - $grouped = $files->groupBy('file_type')->map(function ($group) { + $grouped = $files->groupBy('field_key')->map(function ($group) { return $group->map(fn ($file) => $this->formatFileResponse($file))->values(); }); @@ -115,6 +115,10 @@ public function upload(int $id, ItemFileUploadRequest $request) // 파일 저장 (tenant 디스크) Storage::disk('tenant')->putFileAs($directory, $uploadedFile, $storedName); + // file_type 자동 분류 (MIME 타입 기반) + $mimeType = $uploadedFile->getMimeType(); + $fileType = $this->detectFileType($mimeType); + // files 테이블에 저장 $file = File::create([ 'tenant_id' => $tenantId, @@ -122,8 +126,9 @@ public function upload(int $id, ItemFileUploadRequest $request) 'stored_name' => $storedName, 'file_path' => $filePath, 'file_size' => $uploadedFile->getSize(), - 'mime_type' => $uploadedFile->getMimeType(), - 'file_type' => $fieldKey, // field_key + 'mime_type' => $mimeType, + 'file_type' => $fileType, // 파일 형식 (image, document, excel, archive) + 'field_key' => $fieldKey, // 비즈니스 용도 (drawing, certificate 등) 'document_id' => $id, 'document_type' => self::ITEM_GROUP_ID, // group_id 'is_temp' => false, @@ -215,7 +220,8 @@ private function formatFileResponse(File $file): array 'file_url' => $this->getFileUrl($file->file_path), 'file_size' => $file->file_size, 'mime_type' => $file->mime_type, - 'field_key' => $file->file_type, + 'file_type' => $file->file_type, + 'field_key' => $file->field_key, 'created_at' => $file->created_at?->format('Y-m-d H:i:s'), ]; } @@ -227,4 +233,34 @@ private function getFileUrl(string $filePath): string { return url('/api/v1/files/download/'.base64_encode($filePath)); } + + /** + * MIME 타입 기반 파일 형식 분류 + */ + private function detectFileType(string $mimeType): string + { + if (str_starts_with($mimeType, 'image/')) { + return 'image'; + } + + if (in_array($mimeType, [ + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'text/csv', + ])) { + return 'excel'; + } + + if (in_array($mimeType, [ + 'application/zip', + 'application/x-rar-compressed', + 'application/x-7z-compressed', + 'application/gzip', + ])) { + return 'archive'; + } + + // 기본값: document (pdf, doc, hwp 등) + return 'document'; + } } diff --git a/app/Models/Commons/File.php b/app/Models/Commons/File.php index dea6ce1..697b493 100644 --- a/app/Models/Commons/File.php +++ b/app/Models/Commons/File.php @@ -38,6 +38,7 @@ class File extends Model 'folder_id', 'is_temp', 'file_type', + 'field_key', 'document_id', 'document_type', 'file_size', diff --git a/app/Services/ItemsService.php b/app/Services/ItemsService.php index db55dc2..e5305d1 100644 --- a/app/Services/ItemsService.php +++ b/app/Services/ItemsService.php @@ -1147,7 +1147,7 @@ private function getItemFiles(int $itemId, int $tenantId): array return []; } - return $files->groupBy('file_type')->map(function ($group) { + return $files->groupBy('field_key')->map(function ($group) { return $group->map(fn ($file) => [ 'id' => $file->id, 'file_name' => $file->display_name ?? $file->file_name, diff --git a/database/migrations/2025_12_12_100000_rollback_items_migration.php b/database/migrations/2025_12_12_100000_rollback_items_migration.php index 4beb985..a6c941d 100644 --- a/database/migrations/2025_12_12_100000_rollback_items_migration.php +++ b/database/migrations/2025_12_12_100000_rollback_items_migration.php @@ -17,26 +17,26 @@ { public function up(): void { - // 1. items 테이블 데이터 삭제 - if (Schema::hasTable('items')) { - $itemsCount = DB::table('items')->count(); - echo "items 테이블 데이터 삭제: {$itemsCount}건\n"; - DB::table('items')->truncate(); - } + // FK 제약 조건 비활성화 + DB::statement('SET FOREIGN_KEY_CHECKS=0'); - // 2. item_id_mappings 테이블 삭제 + // 1. item_id_mappings 테이블 먼저 삭제 (FK 참조) if (Schema::hasTable('item_id_mappings')) { $mappingsCount = DB::table('item_id_mappings')->count(); echo "item_id_mappings 테이블 삭제: {$mappingsCount}건\n"; Schema::dropIfExists('item_id_mappings'); } - // 3. items 테이블 삭제 + // 2. items 테이블 삭제 if (Schema::hasTable('items')) { - echo "items 테이블 삭제\n"; + $itemsCount = DB::table('items')->count(); + echo "items 테이블 삭제: {$itemsCount}건\n"; Schema::dropIfExists('items'); } + // FK 제약 조건 재활성화 + DB::statement('SET FOREIGN_KEY_CHECKS=1'); + echo "✅ Items 관련 테이블 롤백 완료\n"; } diff --git a/database/migrations/2025_12_12_182336_alter_files_table_add_field_key_and_change_file_type.php b/database/migrations/2025_12_12_182336_alter_files_table_add_field_key_and_change_file_type.php new file mode 100644 index 0000000..36ba1f2 --- /dev/null +++ b/database/migrations/2025_12_12_182336_alter_files_table_add_field_key_and_change_file_type.php @@ -0,0 +1,48 @@ +string('field_key', 100) + ->nullable() + ->after('file_type') + ->comment('비즈니스 용도 구분 (drawing, certificate, specification 등)'); + + // 인덱스 추가 (document_type + document_id + field_key 조합 조회용) + $table->index(['document_type', 'document_id', 'field_key'], 'idx_files_document_field_key'); + }); + } + } + + public function down(): void + { + // 1. field_key 컬럼 삭제 + if (Schema::hasColumn('files', 'field_key')) { + Schema::table('files', function (Blueprint $table) { + $table->dropIndex('idx_files_document_field_key'); + $table->dropColumn('field_key'); + }); + } + + // 2. file_type VARCHAR → ENUM 복원 + DB::statement("ALTER TABLE `files` MODIFY `file_type` ENUM('document','image','excel','archive') NOT NULL"); + } +};