feat: [quality] 품질관리서 파일 업로드/삭제 API
- POST /{id}/upload-file: 1건당 1파일, 기존 파일 교체
- DELETE /{id}/file: 파일 soft delete
- QualityDocument.file() relation 추가
- R2 저장 경로: {tenant_id}/quality-documents/{year}/{month}
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -124,4 +124,24 @@ public function resultDocument(int $id)
|
||||
return $this->service->resultDocument($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function uploadFile(Request $request, int $id)
|
||||
{
|
||||
$request->validate([
|
||||
'file' => ['required', 'file', 'max:51200'], // 50MB
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->service->uploadFile($id, $request->file('file'));
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
public function deleteFile(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$this->service->deleteFile($id);
|
||||
|
||||
return 'success';
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Models\Commons\File;
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Orders\Client;
|
||||
use App\Traits\Auditable;
|
||||
@@ -72,6 +73,12 @@ public function performanceReport()
|
||||
return $this->hasOne(PerformanceReport::class);
|
||||
}
|
||||
|
||||
public function file()
|
||||
{
|
||||
return $this->hasOne(File::class, 'document_id')
|
||||
->where('document_type', static::class);
|
||||
}
|
||||
|
||||
// ===== 채번 =====
|
||||
|
||||
public static function generateDocNumber(int $tenantId): string
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Commons\File;
|
||||
use App\Models\Documents\Document;
|
||||
use App\Models\Documents\DocumentData;
|
||||
use App\Models\Documents\DocumentTemplate;
|
||||
@@ -13,7 +14,9 @@
|
||||
use App\Models\Qualitys\QualityDocumentLocation;
|
||||
use App\Models\Qualitys\QualityDocumentOrder;
|
||||
use App\Services\Audit\AuditLogger;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
@@ -1248,4 +1251,78 @@ private function formatInspectionPeriod(array $options): string
|
||||
|
||||
return $start ?: $end ?: '';
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 파일 업로드/삭제
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 품질관리서 파일 업로드 (1건당 1파일, 기존 파일 있으면 교체)
|
||||
*/
|
||||
public function uploadFile(int $id, UploadedFile $uploadedFile): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
$doc = QualityDocument::where('tenant_id', $tenantId)->findOrFail($id);
|
||||
|
||||
// 기존 파일이 있으면 물리 삭제 (교체)
|
||||
$existingFile = $doc->file;
|
||||
if ($existingFile) {
|
||||
$existingFile->permanentDelete();
|
||||
}
|
||||
|
||||
// 저장 경로: {tenant_id}/quality-documents/{year}/{month}/{stored_name}
|
||||
$date = now();
|
||||
$storedName = bin2hex(random_bytes(16)).'.'.$uploadedFile->getClientOriginalExtension();
|
||||
$filePath = sprintf(
|
||||
'%d/quality-documents/%s/%s/%s',
|
||||
$tenantId,
|
||||
$date->format('Y'),
|
||||
$date->format('m'),
|
||||
$storedName
|
||||
);
|
||||
|
||||
// R2에 파일 저장
|
||||
Storage::disk('r2')->put($filePath, file_get_contents($uploadedFile->getPathname()));
|
||||
|
||||
// DB 레코드 생성
|
||||
$file = File::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'document_type' => QualityDocument::class,
|
||||
'document_id' => $doc->id,
|
||||
'display_name' => $uploadedFile->getClientOriginalName(),
|
||||
'stored_name' => $storedName,
|
||||
'file_path' => $filePath,
|
||||
'file_size' => $uploadedFile->getSize(),
|
||||
'mime_type' => $uploadedFile->getClientMimeType(),
|
||||
'uploaded_by' => $userId,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
|
||||
return [
|
||||
'id' => $file->id,
|
||||
'display_name' => $file->display_name,
|
||||
'file_size' => $file->file_size,
|
||||
'mime_type' => $file->mime_type,
|
||||
'created_at' => $file->created_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 품질관리서 파일 삭제
|
||||
*/
|
||||
public function deleteFile(int $id): void
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$doc = QualityDocument::where('tenant_id', $tenantId)->findOrFail($id);
|
||||
|
||||
$file = $doc->file;
|
||||
if (! $file) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
$file->softDeleteFile($this->apiUserId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
Route::post('/{id}/locations/{locId}/inspect', [QualityDocumentController::class, 'inspectLocation'])->whereNumber('id')->whereNumber('locId')->name('v1.quality.documents.inspect-location');
|
||||
Route::get('/{id}/request-document', [QualityDocumentController::class, 'requestDocument'])->whereNumber('id')->name('v1.quality.documents.request-document');
|
||||
Route::get('/{id}/result-document', [QualityDocumentController::class, 'resultDocument'])->whereNumber('id')->name('v1.quality.documents.result-document');
|
||||
Route::post('/{id}/upload-file', [QualityDocumentController::class, 'uploadFile'])->whereNumber('id')->name('v1.quality.documents.upload-file');
|
||||
Route::delete('/{id}/file', [QualityDocumentController::class, 'deleteFile'])->whereNumber('id')->name('v1.quality.documents.delete-file');
|
||||
});
|
||||
|
||||
// 실적신고
|
||||
|
||||
Reference in New Issue
Block a user