feat:서명 완료 시 계약서 이메일 발송 및 감사 로그 추가
- EsignCompletedMail Mailable 생성 (완료 알림 + PDF 다운로드 링크) - completed.blade.php 이메일 뷰 템플릿 생성 (초록색 테마) - submitSignature에 contract_completed 감사 로그 추가 - 모든 서명자에게 완료 이메일 발송 + completion_email_sent 감사 로그 - 이메일 발송 실패 시 try-catch로 계약 완료 보호 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,9 @@
|
||||
use App\Models\ESign\EsignSigner;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Mail\EsignCompletedMail;
|
||||
use App\Mail\EsignRequestMail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Services\ESign\PdfSignatureService;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -250,11 +252,49 @@ public function submitSignature(Request $request, string $token): JsonResponse
|
||||
$pdfService = new PdfSignatureService();
|
||||
$pdfService->mergeSignatures($contract);
|
||||
} catch (\Throwable $e) {
|
||||
\Illuminate\Support\Facades\Log::error('PDF 서명 합성 실패', [
|
||||
Log::error('PDF 서명 합성 실패', [
|
||||
'contract_id' => $contract->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
// 계약 완료 감사 로그
|
||||
EsignAuditLog::create([
|
||||
'tenant_id' => $contract->tenant_id,
|
||||
'contract_id' => $contract->id,
|
||||
'signer_id' => $signer->id,
|
||||
'action' => 'contract_completed',
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'metadata' => ['total_signers' => $allSigners->count()],
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
// 모든 서명자에게 완료 이메일 발송
|
||||
foreach ($allSigners as $completedSigner) {
|
||||
try {
|
||||
Mail::to($completedSigner->email)->send(
|
||||
new EsignCompletedMail($contract, $completedSigner, $allSigners)
|
||||
);
|
||||
|
||||
EsignAuditLog::create([
|
||||
'tenant_id' => $contract->tenant_id,
|
||||
'contract_id' => $contract->id,
|
||||
'signer_id' => $completedSigner->id,
|
||||
'action' => 'completion_email_sent',
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'metadata' => ['email' => $completedSigner->email],
|
||||
'created_at' => now(),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('계약 완료 이메일 발송 실패', [
|
||||
'contract_id' => $contract->id,
|
||||
'signer_id' => $completedSigner->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$contract->update(['status' => 'partially_signed']);
|
||||
|
||||
|
||||
46
app/Mail/EsignCompletedMail.php
Normal file
46
app/Mail/EsignCompletedMail.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\ESign\EsignContract;
|
||||
use App\Models\ESign\EsignSigner;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class EsignCompletedMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public EsignContract $contract,
|
||||
public EsignSigner $signer,
|
||||
public Collection $allSigners,
|
||||
) {}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: "[SAM] 전자계약 서명 완료 - {$this->contract->title}",
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
$downloadUrl = config('app.url') . '/esign/sign/' . $this->signer->access_token . '/api/document';
|
||||
|
||||
return new Content(
|
||||
html: 'emails.esign.completed',
|
||||
with: [
|
||||
'contractTitle' => $this->contract->title,
|
||||
'signerName' => $this->signer->name,
|
||||
'completedAt' => $this->contract->completed_at?->format('Y-m-d H:i'),
|
||||
'allSigners' => $this->allSigners,
|
||||
'downloadUrl' => $downloadUrl,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
89
resources/views/emails/esign/completed.blade.php
Normal file
89
resources/views/emails/esign/completed.blade.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>전자계약 서명 완료</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #f5f5f5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="padding: 40px 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background-color: #16a34a; padding: 32px 40px; text-align: center;">
|
||||
<h1 style="margin: 0; color: #ffffff; font-size: 24px; font-weight: 600;">
|
||||
전자계약 서명 완료
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="padding: 40px;">
|
||||
<p style="margin: 0 0 16px; font-size: 16px; color: #333;">안녕하세요, <strong>{{ $signerName }}</strong>님.</p>
|
||||
<p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
|
||||
아래 전자계약의 모든 서명이 완료되었습니다.<br>
|
||||
완성된 계약서를 다운로드하여 보관해 주세요.
|
||||
</p>
|
||||
|
||||
<!-- 계약 정보 -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f0fdf4; border-radius: 6px; margin-bottom: 24px;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
<p style="margin: 0 0 8px; font-size: 13px; color: #888;">계약 제목</p>
|
||||
<p style="margin: 0 0 16px; font-size: 16px; color: #333; font-weight: 600;">{{ $contractTitle }}</p>
|
||||
<p style="margin: 0 0 8px; font-size: 13px; color: #888;">완료 일시</p>
|
||||
<p style="margin: 0; font-size: 15px; color: #16a34a; font-weight: 600;">{{ $completedAt }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- 서명자 목록 -->
|
||||
<p style="margin: 0 0 12px; font-size: 14px; color: #555; font-weight: 600;">서명자 목록</p>
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="border: 1px solid #e5e7eb; border-radius: 6px; overflow: hidden; margin-bottom: 24px;">
|
||||
<tr style="background-color: #f9fafb;">
|
||||
<td style="padding: 10px 16px; font-size: 13px; color: #666; font-weight: 600; border-bottom: 1px solid #e5e7eb;">서명자</td>
|
||||
<td style="padding: 10px 16px; font-size: 13px; color: #666; font-weight: 600; border-bottom: 1px solid #e5e7eb;">서명 일시</td>
|
||||
<td style="padding: 10px 16px; font-size: 13px; color: #666; font-weight: 600; border-bottom: 1px solid #e5e7eb; text-align: center;">상태</td>
|
||||
</tr>
|
||||
@foreach($allSigners as $s)
|
||||
<tr>
|
||||
<td style="padding: 12px 16px; font-size: 14px; color: #333; border-bottom: 1px solid #f3f4f6;">{{ $s->name }}</td>
|
||||
<td style="padding: 12px 16px; font-size: 14px; color: #555; border-bottom: 1px solid #f3f4f6;">{{ $s->signed_at?->format('Y-m-d H:i') ?? '-' }}</td>
|
||||
<td style="padding: 12px 16px; font-size: 14px; color: #16a34a; border-bottom: 1px solid #f3f4f6; text-align: center;">✓</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
|
||||
<!-- 다운로드 버튼 -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 8px 0;">
|
||||
<a href="{{ $downloadUrl }}" style="display: inline-block; background-color: #16a34a; color: #ffffff; text-decoration: none; padding: 14px 40px; border-radius: 6px; font-size: 16px; font-weight: 600;">
|
||||
계약서 다운로드
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin: 24px 0 0; font-size: 13px; color: #999; line-height: 1.6;">
|
||||
위 버튼이 동작하지 않으면 아래 링크를 브라우저에 직접 입력해 주세요:<br>
|
||||
<a href="{{ $downloadUrl }}" style="color: #16a34a; word-break: break-all;">{{ $downloadUrl }}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f8fafc; padding: 24px 40px; text-align: center; border-top: 1px solid #e5e7eb;">
|
||||
<p style="margin: 0; font-size: 12px; color: #999;">
|
||||
본 메일은 SAM 전자계약 시스템에서 자동 발송되었습니다.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user