From 9b119fa7c99d7b00b3f86f1aad9cdc768dfdf96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Thu, 12 Feb 2026 20:05:28 +0900 Subject: [PATCH] =?UTF-8?q?fix:=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=8B=9C=20PDF=20=ED=8C=8C=EC=9D=BC=20=ED=95=84?= =?UTF-8?q?=EC=88=98=20=ED=95=B4=EC=A0=9C=20=EB=B0=8F=20=ED=9B=84=EC=86=8D?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - create: 템플릿 선택 시 PDF required 제거, 안내 메시지 표시 - fields: PDF 없는 계약 시 업로드 UI 표시 - API: uploadPdf 엔드포인트 추가 (POST /{id}/upload-pdf) Co-Authored-By: Claude Opus 4.6 --- .../Controllers/ESign/EsignApiController.php | 34 ++++++++++++++++++ resources/views/esign/create.blade.php | 5 +-- resources/views/esign/fields.blade.php | 36 +++++++++++++++++++ routes/web.php | 1 + 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/ESign/EsignApiController.php b/app/Http/Controllers/ESign/EsignApiController.php index 30722eb7..4ea39061 100644 --- a/app/Http/Controllers/ESign/EsignApiController.php +++ b/app/Http/Controllers/ESign/EsignApiController.php @@ -354,6 +354,40 @@ public function download(int $id) ]); } + /** + * PDF 업로드 (PDF 없이 생성된 계약에 나중에 업로드) + */ + public function uploadPdf(Request $request, int $id): JsonResponse + { + $request->validate([ + 'file' => 'required|file|mimes:pdf|max:20480', + ]); + + $tenantId = session('selected_tenant_id', 1); + $contract = EsignContract::forTenant($tenantId)->findOrFail($id); + + if ($contract->original_file_path) { + return response()->json(['success' => false, 'message' => '이미 PDF 파일이 존재합니다.'], 422); + } + + $file = $request->file('file'); + $filePath = $file->store("esign/{$tenantId}/contracts", 'local'); + + $contract->update([ + 'original_file_path' => $filePath, + 'original_file_name' => $file->getClientOriginalName(), + 'original_file_hash' => hash_file('sha256', $file->getRealPath()), + 'original_file_size' => $file->getSize(), + 'updated_by' => auth()->id(), + ]); + + return response()->json([ + 'success' => true, + 'message' => 'PDF 파일이 업로드되었습니다.', + 'data' => ['path' => $filePath, 'name' => $file->getClientOriginalName()], + ]); + } + // ─── 필드 템플릿 관련 메서드 ─── /** diff --git a/resources/views/esign/create.blade.php b/resources/views/esign/create.blade.php index 2789fe9b..a967cbb1 100644 --- a/resources/views/esign/create.blade.php +++ b/resources/views/esign/create.blade.php @@ -163,10 +163,11 @@ className="w-8 h-8 flex items-center justify-center rounded-lg text-amber-500 ho className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors" />
- - setFile(e.target.files[0])} required + + setFile(e.target.files[0])} required={!templateId} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm file:mr-3 file:py-1 file:px-3 file:rounded file:border-0 file:bg-blue-50 file:text-blue-700 file:text-sm file:font-medium file:cursor-pointer" /> {file &&

{file.name} ({(file.size / 1024 / 1024).toFixed(2)} MB)

} + {templateId && !file &&

템플릿 선택 시 PDF 파일은 선택사항입니다. 나중에 업로드할 수 있습니다.

}
diff --git a/resources/views/esign/fields.blade.php b/resources/views/esign/fields.blade.php index d801efa4..f717d723 100644 --- a/resources/views/esign/fields.blade.php +++ b/resources/views/esign/fields.blade.php @@ -718,9 +718,11 @@ className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-7 // PDF 로드 const loadPdf = useCallback(async () => { if (!contract) return; + if (!contract.original_file_path) return; // PDF 없는 계약 try { const url = `/esign/contracts/${CONTRACT_ID}/download`; const res = await fetch(url, { headers: { 'X-CSRF-TOKEN': csrfToken } }); + if (!res.ok) return; const arrayBuffer = await res.arrayBuffer(); const doc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; setPdfDoc(doc); @@ -728,6 +730,24 @@ className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-7 } catch (e) { console.error('PDF 로드 실패:', e); } }, [contract]); + // PDF 파일 업로드 (PDF 없는 계약용) + const uploadPdf = async (file) => { + const fd = new FormData(); + fd.append('file', file); + fd.append('_token', csrfToken); + try { + const res = await fetch(`/esign/contracts/${CONTRACT_ID}/upload-pdf`, { + method: 'POST', + headers: { 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken }, + body: fd, + }); + const json = await res.json(); + if (json.success) { + setContract(prev => ({ ...prev, original_file_path: json.data.path, original_file_name: json.data.name })); + } + } catch (e) { console.error('PDF 업로드 실패:', e); } + }; + // PDF 페이지 렌더링 const renderPage = useCallback(async (pageNum) => { if (!pdfDoc || !canvasRef.current) return; @@ -1054,6 +1074,21 @@ className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-7 {/* PDF 뷰어 (중앙) */}
setSelectedFieldIndex(null)}> + {!pdfDoc && !contract.original_file_path ? ( +
+
+ + + +

PDF 파일이 없습니다. 파일을 업로드하면 서명 위치를 설정할 수 있습니다.

+ +
+
+ ) : (
@@ -1086,6 +1121,7 @@ className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-7
+ )}
{/* 속성 패널 (우측) */} diff --git a/routes/web.php b/routes/web.php index a7ab9fc7..fd17b57d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1414,6 +1414,7 @@ Route::post('/{id}/send', [EsignApiController::class, 'send'])->whereNumber('id')->name('send'); Route::post('/{id}/remind', [EsignApiController::class, 'remind'])->whereNumber('id')->name('remind'); Route::get('/{id}/download', [EsignApiController::class, 'download'])->whereNumber('id')->name('download'); + Route::post('/{id}/upload-pdf', [EsignApiController::class, 'uploadPdf'])->whereNumber('id')->name('upload-pdf'); // 필드 템플릿 Route::get('/templates', [EsignApiController::class, 'indexTemplates'])->name('templates.index');