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');