diff --git a/app/Http/Controllers/ESign/EsignApiController.php b/app/Http/Controllers/ESign/EsignApiController.php index 4ea39061..440106a6 100644 --- a/app/Http/Controllers/ESign/EsignApiController.php +++ b/app/Http/Controllers/ESign/EsignApiController.php @@ -14,6 +14,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; class EsignApiController extends Controller @@ -88,6 +89,7 @@ public function store(Request $request): JsonResponse 'sign_order_type' => 'required|in:counterpart_first,creator_first', 'expires_at' => 'nullable|date', 'expires_days' => 'nullable|integer|min:1|max:365', + 'template_id' => 'nullable|integer', 'signers' => 'required|array|size:2', 'signers.*.name' => 'required|string|max:100', 'signers.*.email' => 'required|email|max:200', @@ -108,11 +110,28 @@ public function store(Request $request): JsonResponse $fileSize = null; if ($request->hasFile('file')) { + // 사용자가 직접 업로드한 파일 우선 사용 $file = $request->file('file'); $fileName = $file->getClientOriginalName(); $fileSize = $file->getSize(); $fileHash = hash_file('sha256', $file->getRealPath()); $filePath = $file->store("esign/{$tenantId}/contracts", 'local'); + } elseif ($request->input('template_id')) { + // 템플릿에 PDF가 있으면 복사 + $template = EsignFieldTemplate::forTenant($tenantId) + ->where('is_active', true) + ->find($request->input('template_id')); + + if ($template && $template->file_path && Storage::disk('local')->exists($template->file_path)) { + $ext = pathinfo($template->file_path, PATHINFO_EXTENSION) ?: 'pdf'; + $newPath = "esign/{$tenantId}/contracts/" . Str::random(40) . ".{$ext}"; + Storage::disk('local')->copy($template->file_path, $newPath); + + $filePath = $newPath; + $fileName = $template->file_name; + $fileHash = $template->file_hash; + $fileSize = $template->file_size; + } } $contract = EsignContract::create([ @@ -388,6 +407,25 @@ public function uploadPdf(Request $request, int $id): JsonResponse ]); } + /** + * 템플릿 PDF 다운로드 + */ + public function downloadTemplatePdf(int $id) + { + $tenantId = session('selected_tenant_id', 1); + $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id); + + if (!$template->file_path || !Storage::disk('local')->exists($template->file_path)) { + abort(404, 'PDF 파일을 찾을 수 없습니다.'); + } + + $fileName = $template->file_name ?: 'template.pdf'; + + return Storage::disk('local')->download($template->file_path, $fileName, [ + 'Content-Type' => 'application/pdf', + ]); + } + // ─── 필드 템플릿 관련 메서드 ─── /** @@ -423,6 +461,8 @@ public function storeTemplate(Request $request): JsonResponse 'name' => 'required|string|max:100', 'description' => 'nullable|string', 'category' => 'nullable|string|max:50', + 'include_pdf' => 'nullable|boolean', + 'contract_id' => 'nullable|integer', 'items' => 'required|array|min:1', 'items.*.signer_order' => 'required|integer|min:1', 'items.*.page_number' => 'required|integer|min:1', @@ -441,8 +481,36 @@ public function storeTemplate(Request $request): JsonResponse $items = $request->input('items'); $signerCount = max(array_column($items, 'signer_order')); - $template = DB::transaction(function () use ($tenantId, $request, $items, $signerCount) { - $template = EsignFieldTemplate::create([ + // PDF 포함 여부 확인 + $includePdf = $request->boolean('include_pdf'); + $contractId = $request->input('contract_id'); + $sourceContract = null; + + if ($includePdf && $contractId) { + $sourceContract = EsignContract::forTenant($tenantId)->find($contractId); + if (!$sourceContract || !$sourceContract->original_file_path || !Storage::disk('local')->exists($sourceContract->original_file_path)) { + $sourceContract = null; + } + } + + $template = DB::transaction(function () use ($tenantId, $request, $items, $signerCount, $sourceContract) { + $fileData = []; + + if ($sourceContract) { + $timestamp = now()->format('YmdHis'); + $ext = pathinfo($sourceContract->original_file_path, PATHINFO_EXTENSION) ?: 'pdf'; + $newPath = "esign/{$tenantId}/templates/{$timestamp}.{$ext}"; + Storage::disk('local')->copy($sourceContract->original_file_path, $newPath); + + $fileData = [ + 'file_path' => $newPath, + 'file_name' => $sourceContract->original_file_name, + 'file_hash' => $sourceContract->original_file_hash, + 'file_size' => $sourceContract->original_file_size, + ]; + } + + $template = EsignFieldTemplate::create(array_merge([ 'tenant_id' => $tenantId, 'name' => $request->input('name'), 'description' => $request->input('description'), @@ -450,7 +518,7 @@ public function storeTemplate(Request $request): JsonResponse 'signer_count' => $signerCount, 'is_active' => true, 'created_by' => auth()->id(), - ]); + ], $fileData)); foreach ($items as $i => $item) { EsignFieldTemplateItem::create([ @@ -531,7 +599,22 @@ public function duplicateTemplate(int $id): JsonResponse ->findOrFail($id); $newTemplate = DB::transaction(function () use ($template, $tenantId) { - $newTemplate = EsignFieldTemplate::create([ + $fileData = []; + if ($template->file_path && Storage::disk('local')->exists($template->file_path)) { + $timestamp = now()->format('YmdHis'); + $ext = pathinfo($template->file_path, PATHINFO_EXTENSION) ?: 'pdf'; + $newPath = "esign/{$tenantId}/templates/{$timestamp}_copy.{$ext}"; + Storage::disk('local')->copy($template->file_path, $newPath); + + $fileData = [ + 'file_path' => $newPath, + 'file_name' => $template->file_name, + 'file_hash' => $template->file_hash, + 'file_size' => $template->file_size, + ]; + } + + $newTemplate = EsignFieldTemplate::create(array_merge([ 'tenant_id' => $tenantId, 'name' => $template->name . ' (복사)', 'description' => $template->description, @@ -539,7 +622,7 @@ public function duplicateTemplate(int $id): JsonResponse 'signer_count' => $template->signer_count, 'is_active' => true, 'created_by' => auth()->id(), - ]); + ], $fileData)); foreach ($template->items as $item) { EsignFieldTemplateItem::create([ diff --git a/app/Models/ESign/EsignFieldTemplate.php b/app/Models/ESign/EsignFieldTemplate.php index c496c21a..d346eb7d 100644 --- a/app/Models/ESign/EsignFieldTemplate.php +++ b/app/Models/ESign/EsignFieldTemplate.php @@ -15,6 +15,10 @@ class EsignFieldTemplate extends Model 'name', 'description', 'category', + 'file_path', + 'file_name', + 'file_hash', + 'file_size', 'signer_count', 'is_active', 'created_by', diff --git a/resources/views/esign/create.blade.php b/resources/views/esign/create.blade.php index a967cbb1..857fcb05 100644 --- a/resources/views/esign/create.blade.php +++ b/resources/views/esign/create.blade.php @@ -101,6 +101,7 @@ className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring const fd = new FormData(); Object.entries(form).forEach(([k, v]) => { if (v) fd.append(k, v); }); if (file) fd.append('file', file); + if (templateId) fd.append('template_id', templateId); try { fd.append('_token', csrfToken); @@ -167,7 +168,13 @@ className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring 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 파일은 선택사항입니다. 나중에 업로드할 수 있습니다.
} + {templateId && !file && (() => { + const selectedTpl = templates.find(t => t.id == templateId); + if (selectedTpl?.file_path) { + return📄 템플릿에 포함된 PDF가 자동으로 사용됩니다. 별도 파일을 업로드하면 해당 파일이 우선 적용됩니다.
; + } + return템플릿 선택 시 PDF 파일은 선택사항입니다. 나중에 업로드할 수 있습니다.
; + })()}{template.description}
)} + {/* PDF 포함 표시 */} + {template.file_path && ( +