Files
sam-api/app/Http/Controllers/Api/V1/ItemsFileController.php
kent 20ad6da164 fix: P0 Critical 이슈 수정 - 삭제된 Product/Material 참조 제거
- ItemsBomController: ProductBomService 제거, Item.bom JSON 기반으로 재구현
- ItemsFileController: Product/Material → Item 모델로 통합
- ItemPage: products/materials 클래스 매핑 제거
- ItemsService 삭제 (1,210줄) - ItemService로 대체 완료

BOM 기능 변경:
- 기존: ProductBomService (삭제됨)
- 변경: Item 모델의 bom JSON 필드 직접 조작
- 모든 BOM API 엔드포인트 정상 동작

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-14 01:10:25 +09:00

256 lines
7.9 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1;
use App\Helpers\ApiResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\Item\ItemFileUploadRequest;
use App\Models\Commons\File;
use App\Models\Items\Item;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* 품목 파일 관리 컨트롤러
*
* group_id 기반 파일 관리, field_key 동적 지원
* - document_type: group_id (품목 그룹)
* - document_id: item_id (품목 ID)
* - file_type: field_key (동적)
*/
class ItemsFileController extends Controller
{
/**
* 품목 그룹 ID (고정값, 추후 설정으로 변경 가능)
*/
private const ITEM_GROUP_ID = '1';
/**
* 파일 목록 조회
*
* GET /api/v1/items/{id}/files
*/
public function index(int $id, Request $request)
{
return ApiResponse::handle(function () use ($id, $request) {
$tenantId = app('tenant_id');
$fieldKey = $request->input('field_key');
// 품목 존재 확인
$this->getItemById($id, $tenantId);
// 파일 조회
$query = File::query()
->where('tenant_id', $tenantId)
->where('document_type', self::ITEM_GROUP_ID)
->where('document_id', $id);
// 특정 field_key만 조회
if ($fieldKey) {
$query->where('field_key', $fieldKey);
}
$files = $query->orderBy('created_at', 'desc')->get();
// field_key별 그룹핑
$grouped = $files->groupBy('field_key')->map(function ($group) {
return $group->map(fn ($file) => $this->formatFileResponse($file))->values();
});
return $grouped;
}, __('message.fetched'));
}
/**
* 파일 업로드
*
* POST /api/v1/items/{id}/files
*/
public function upload(int $id, ItemFileUploadRequest $request)
{
return ApiResponse::handle(function () use ($id, $request) {
$tenantId = app('tenant_id');
$userId = auth()->id();
$validated = $request->validated();
$fieldKey = $validated['field_key'];
$uploadedFile = $validated['file'];
$existingFileId = $validated['file_id'] ?? null;
// 품목 존재 확인
$this->getItemById($id, $tenantId);
$replaced = false;
// 기존 파일 교체 (file_id가 있는 경우)
if ($existingFileId) {
$existingFile = File::query()
->where('tenant_id', $tenantId)
->where('document_type', self::ITEM_GROUP_ID)
->where('document_id', $id)
->where('id', $existingFileId)
->first();
if ($existingFile) {
$existingFile->softDeleteFile($userId);
$replaced = true;
}
}
// 파일명 생성 (64bit 난수)
$extension = $uploadedFile->getClientOriginalExtension();
$storedName = bin2hex(random_bytes(8)).'.'.$extension;
$displayName = $uploadedFile->getClientOriginalName();
// 경로 생성: tenants/{tenant_id}/items/{year}/{month}/{stored_name}
$year = date('Y');
$month = date('m');
$directory = sprintf('%d/items/%s/%s', $tenantId, $year, $month);
$filePath = $directory.'/'.$storedName;
// 파일 저장 (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,
'display_name' => $displayName,
'stored_name' => $storedName,
'file_path' => $filePath,
'file_size' => $uploadedFile->getSize(),
'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,
'uploaded_by' => $userId,
'created_by' => $userId,
]);
return [
'file_id' => $file->id,
'field_key' => $fieldKey,
'file_url' => $this->getFileUrl($filePath),
'file_path' => $filePath,
'file_name' => $displayName,
'file_size' => $file->file_size,
'mime_type' => $file->mime_type,
'replaced' => $replaced,
];
}, __('message.file.uploaded'));
}
/**
* 파일 삭제
*
* DELETE /api/v1/items/{id}/files/{fileId}
*/
public function delete(int $id, int $fileId, Request $request)
{
return ApiResponse::handle(function () use ($id, $fileId) {
$tenantId = app('tenant_id');
$userId = auth()->id();
// 품목 존재 확인
$this->getItemById($id, $tenantId);
// 파일 조회
$file = File::query()
->where('tenant_id', $tenantId)
->where('document_type', self::ITEM_GROUP_ID)
->where('document_id', $id)
->where('id', $fileId)
->first();
if (! $file) {
throw new NotFoundHttpException(__('error.file.not_found'));
}
// Soft delete
$file->softDeleteFile($userId);
return [
'file_id' => $fileId,
'deleted' => true,
];
}, __('message.file.deleted'));
}
/**
* ID로 품목 조회 (통합 items 테이블)
*/
private function getItemById(int $id, int $tenantId): Item
{
$item = Item::query()
->where('tenant_id', $tenantId)
->find($id);
if (! $item) {
throw new NotFoundHttpException(__('error.not_found'));
}
return $item;
}
/**
* 파일 응답 포맷
*/
private function formatFileResponse(File $file): array
{
return [
'id' => $file->id,
'file_name' => $file->display_name,
'file_path' => $file->file_path,
'file_url' => $this->getFileUrl($file->file_path),
'file_size' => $file->file_size,
'mime_type' => $file->mime_type,
'file_type' => $file->file_type,
'field_key' => $file->field_key,
'created_at' => $file->created_at?->format('Y-m-d H:i:s'),
];
}
/**
* 파일 URL 생성
*/
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';
}
}