feat:전자서명 문서 다운로드 시 미리보기 PDF 제공 (서명/도장 제외 필드 합성)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-14 12:57:29 +09:00
parent 992f28ba33
commit 8fe03b57f4
2 changed files with 69 additions and 3 deletions

View File

@@ -388,9 +388,18 @@ public function downloadDocument(string $token): StreamedResponse|JsonResponse
}
// 서명 완료된 PDF가 있으면 우선 제공
$filePath = $contract->signed_file_path && Storage::disk('local')->exists($contract->signed_file_path)
? $contract->signed_file_path
: $contract->original_file_path;
if ($contract->signed_file_path && Storage::disk('local')->exists($contract->signed_file_path)) {
$filePath = $contract->signed_file_path;
} else {
// 서명 전: 텍스트/날짜/체크박스 필드가 합성된 미리보기 PDF 생성
try {
$pdfService = new PdfSignatureService();
$filePath = $pdfService->generatePreview($contract);
} catch (\Throwable $e) {
Log::warning('미리보기 PDF 생성 실패, 원본 제공', ['error' => $e->getMessage()]);
$filePath = $contract->original_file_path;
}
}
if (! Storage::disk('local')->exists($filePath)) {
return response()->json(['success' => false, 'message' => '문서 파일이 존재하지 않습니다.'], 404);

View File

@@ -99,6 +99,63 @@ public function mergeSignatures(EsignContract $contract): string
return $signedRelPath;
}
/**
* 서명/도장을 제외한 필드(텍스트, 날짜, 체크박스)만 합성한 미리보기 PDF를 생성한다.
* 서명 전 문서 확인 시 사용된다.
*/
public function generatePreview(EsignContract $contract): string
{
$disk = Storage::disk('local');
$originalPath = $disk->path($contract->original_file_path);
if (! file_exists($originalPath)) {
throw new \RuntimeException("원본 PDF 파일이 존재하지 않습니다: {$contract->original_file_path}");
}
$pdf = new Fpdi();
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pageCount = $pdf->setSourceFile($originalPath);
// 서명/도장을 제외한 필드만 조회
$signFields = EsignSignField::withoutGlobalScopes()
->where('contract_id', $contract->id)
->whereNotIn('field_type', ['signature', 'stamp'])
->with('signer')
->orderBy('page_number')
->orderBy('sort_order')
->get()
->groupBy('page_number');
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
$templateId = $pdf->importPage($pageNo);
$size = $pdf->getTemplateSize($templateId);
$pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
$pdf->useTemplate($templateId, 0, 0, $size['width'], $size['height']);
if ($signFields->has($pageNo)) {
foreach ($signFields[$pageNo] as $field) {
$this->overlayField($pdf, $field, $size['width'], $size['height']);
}
}
}
// 미리보기 PDF 저장
$previewDir = "esign/{$contract->tenant_id}/preview";
$previewRelPath = "{$previewDir}/{$contract->id}_preview.pdf";
$previewAbsPath = $disk->path($previewRelPath);
if (! is_dir(dirname($previewAbsPath))) {
mkdir(dirname($previewAbsPath), 0755, true);
}
$pdf->Output($previewAbsPath, 'F');
return $previewRelPath;
}
/**
* 개별 필드를 PDF 위에 오버레이한다.
*/