fix:E-Sign PDF 폰트 크기 2배 확대, 왼쪽 정렬 기본값, 서명자 매핑 수정
- PdfSignatureService: 자동 폰트 크기 공식 2배(h*0.7→h*1.4), 기본 정렬 C→L - text_align 필드 추가 (L/C/R 정렬 선택 가능) - store()/buildVariableMap(): sign_order→role 기반 매핑으로 변경 (signer_order 1=creator/갑/회사, 2=counterpart/을/파트너) - template-fields: 가로 정렬 버튼 UI 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -337,9 +337,11 @@ public function store(Request $request): JsonResponse
|
||||
|
||||
if ($template && $template->items->isNotEmpty()) {
|
||||
$contract->loadMissing('signers');
|
||||
// 템플릿 signer_order를 역할(role)로 매핑: 1=creator(갑/회사), 2=counterpart(을/파트너)
|
||||
$signerMap = [];
|
||||
foreach ($contract->signers as $signer) {
|
||||
$signerMap[$signer->sign_order] = $signer->id;
|
||||
$templateOrder = $signer->role === 'creator' ? 1 : 2;
|
||||
$signerMap[$templateOrder] = $signer->id;
|
||||
}
|
||||
|
||||
$variableValues = $this->buildVariableMap($contract, $template);
|
||||
@@ -366,6 +368,7 @@ public function store(Request $request): JsonResponse
|
||||
'field_label' => $item->field_label,
|
||||
'field_variable' => $item->field_variable,
|
||||
'font_size' => $item->font_size,
|
||||
'text_align' => $item->text_align ?? 'L',
|
||||
'field_value' => $fieldValue,
|
||||
'is_required' => $item->is_required,
|
||||
'sort_order' => $item->sort_order,
|
||||
@@ -837,6 +840,7 @@ public function storeTemplate(Request $request): JsonResponse
|
||||
'items.*.field_label' => 'nullable|string|max:100',
|
||||
'items.*.field_variable' => 'nullable|string|max:50',
|
||||
'items.*.font_size' => 'nullable|integer|min:6|max:72',
|
||||
'items.*.text_align' => 'nullable|string|in:L,C,R',
|
||||
'items.*.is_required' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
@@ -900,6 +904,7 @@ public function storeTemplate(Request $request): JsonResponse
|
||||
'field_label' => $item['field_label'] ?? null,
|
||||
'field_variable' => $item['field_variable'] ?? null,
|
||||
'font_size' => $item['font_size'] ?? null,
|
||||
'text_align' => $item['text_align'] ?? 'L',
|
||||
'is_required' => $item['is_required'] ?? true,
|
||||
'sort_order' => $i,
|
||||
]);
|
||||
@@ -1067,6 +1072,7 @@ public function updateTemplateItems(Request $request, int $templateId): JsonResp
|
||||
'items.*.field_label' => 'nullable|string|max:100',
|
||||
'items.*.field_variable' => 'nullable|string|max:50',
|
||||
'items.*.font_size' => 'nullable|integer|min:6|max:72',
|
||||
'items.*.text_align' => 'nullable|string|in:L,C,R',
|
||||
'items.*.is_required' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
@@ -1093,6 +1099,7 @@ public function updateTemplateItems(Request $request, int $templateId): JsonResp
|
||||
'field_label' => $itemData['field_label'] ?? '',
|
||||
'field_variable' => $itemData['field_variable'] ?? null,
|
||||
'font_size' => $itemData['font_size'] ?? null,
|
||||
'text_align' => $itemData['text_align'] ?? 'L',
|
||||
'is_required' => $itemData['is_required'] ?? true,
|
||||
'sort_order' => $i,
|
||||
]);
|
||||
@@ -1340,8 +1347,8 @@ private function buildVariableMap(EsignContract $contract, EsignFieldTemplate $t
|
||||
{
|
||||
$map = [];
|
||||
|
||||
// 시스템 변수: 서명자 정보
|
||||
$signers = $contract->signers->sortBy('sign_order');
|
||||
// 시스템 변수: 서명자 정보 (역할 기반: 1=creator/갑/회사, 2=counterpart/을/파트너)
|
||||
$signers = $contract->signers->sortBy(fn($s) => $s->role === 'creator' ? 1 : 2);
|
||||
$idx = 1;
|
||||
foreach ($signers as $signer) {
|
||||
$map["signer{$idx}_name"] = $signer->name;
|
||||
|
||||
@@ -21,6 +21,7 @@ class EsignFieldTemplateItem extends Model
|
||||
'field_label',
|
||||
'field_variable',
|
||||
'font_size',
|
||||
'text_align',
|
||||
'is_required',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
@@ -22,6 +22,7 @@ class EsignSignField extends Model
|
||||
'field_label',
|
||||
'field_variable',
|
||||
'font_size',
|
||||
'text_align',
|
||||
'field_value',
|
||||
'is_required',
|
||||
'sort_order',
|
||||
|
||||
@@ -168,7 +168,7 @@ private function overlayDate(Fpdi $pdf, EsignSignField $field, float $x, float $
|
||||
$dateText = now()->format('Y-m-d');
|
||||
}
|
||||
|
||||
$this->renderText($pdf, $dateText, $x, $y, $w, $h, $field->font_size);
|
||||
$this->renderText($pdf, $dateText, $x, $y, $w, $h, $field->font_size, $field->text_align ?? 'L');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +181,7 @@ private function overlayText(Fpdi $pdf, EsignSignField $field, float $x, float $
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderText($pdf, $text, $x, $y, $w, $h, $field->font_size);
|
||||
$this->renderText($pdf, $text, $x, $y, $w, $h, $field->font_size, $field->text_align ?? 'L');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,29 +193,30 @@ private function overlayCheckbox(Fpdi $pdf, EsignSignField $field, float $x, flo
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderText($pdf, "\xe2\x9c\x93", $x, $y, $w, $h, $field->font_size); // ✓ (UTF-8)
|
||||
$this->renderText($pdf, "\xe2\x9c\x93", $x, $y, $w, $h, $field->font_size, 'C'); // ✓ (UTF-8)
|
||||
}
|
||||
|
||||
/**
|
||||
* 텍스트를 지정 영역에 렌더링하는 공통 메서드.
|
||||
*/
|
||||
private function renderText(Fpdi $pdf, string $text, float $x, float $y, float $w, float $h, ?int $fieldFontSize = null): void
|
||||
private function renderText(Fpdi $pdf, string $text, float $x, float $y, float $w, float $h, ?int $fieldFontSize = null, string $textAlign = 'L'): void
|
||||
{
|
||||
// 필드에 지정된 폰트 크기 우선, 없으면 영역 높이 기반 자동 산출
|
||||
// 필드에 지정된 폰트 크기 우선, 없으면 영역 높이 기반 자동 산출 (2배 확대)
|
||||
if ($fieldFontSize && $fieldFontSize >= 4) {
|
||||
$fontSize = $fieldFontSize;
|
||||
} else {
|
||||
$fontSize = min($h * 0.7, 12);
|
||||
if ($fontSize < 4) {
|
||||
$fontSize = 4;
|
||||
$fontSize = min($h * 1.4, 24);
|
||||
if ($fontSize < 6) {
|
||||
$fontSize = 6;
|
||||
}
|
||||
}
|
||||
|
||||
$pdf->SetFont($this->getKoreanFont(), '', $fontSize);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
// 텍스트를 영역 중앙에 배치
|
||||
// 텍스트를 지정된 정렬 방식으로 배치 (L=왼쪽, C=가운데, R=오른쪽)
|
||||
$align = in_array($textAlign, ['L', 'C', 'R']) ? $textAlign : 'L';
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($w, $h, $text, 0, 0, 'C', false, '', 0, false, 'T', 'M');
|
||||
$pdf->Cell($w, $h, $text, 0, 0, $align, false, '', 0, false, 'T', 'M');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,6 +461,21 @@ className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
|
||||
className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
|
||||
</div>
|
||||
</div>
|
||||
{['text', 'date'].includes(selectedField.field_type) && (
|
||||
<div>
|
||||
<label className="text-[10px] text-gray-500">가로 정렬</label>
|
||||
<div className="flex gap-1 mt-0.5">
|
||||
{[{ value: 'L', label: '왼쪽', icon: '⫷' }, { value: 'C', label: '가운데', icon: '⫿' }, { value: 'R', label: '오른쪽', icon: '⫸' }].map(a => (
|
||||
<button key={a.value}
|
||||
onClick={() => onUpdateField(selectedFieldIndex, { text_align: a.value })}
|
||||
className={`flex-1 px-2 py-1.5 border rounded text-[10px] font-medium transition-colors ${(selectedField.text_align || 'L') === a.value ? 'bg-blue-600 text-white border-blue-600' : 'text-gray-600 hover:bg-gray-50'}`}
|
||||
title={a.label}>
|
||||
{a.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 pt-1">
|
||||
<label className="flex items-center gap-1.5 text-xs cursor-pointer">
|
||||
<input type="checkbox" checked={selectedField.is_required !== false}
|
||||
@@ -592,6 +607,7 @@ className="text-gray-300 hover:text-red-500 flex-shrink-0 ml-1">×</button>
|
||||
field_label: f.field_label || '',
|
||||
field_variable: f.field_variable || null,
|
||||
font_size: f.font_size || null,
|
||||
text_align: f.text_align || 'L',
|
||||
is_required: f.is_required !== false,
|
||||
}));
|
||||
setFields(loadedFields);
|
||||
@@ -657,7 +673,7 @@ className="text-gray-300 hover:text-red-500 flex-shrink-0 ml-1">×</button>
|
||||
const newField = {
|
||||
signer_order: signerOrder, page_number: currentPage,
|
||||
position_x: 25, position_y: 40, width: 20, height: 5,
|
||||
field_type: fieldType, field_label: '', field_variable: null, font_size: null, is_required: true,
|
||||
field_type: fieldType, field_label: '', field_variable: null, font_size: null, text_align: 'L', is_required: true,
|
||||
};
|
||||
const newFields = [...fields, newField];
|
||||
setFields(newFields);
|
||||
@@ -725,6 +741,7 @@ className="text-gray-300 hover:text-red-500 flex-shrink-0 ml-1">×</button>
|
||||
field_label: f.field_label || '',
|
||||
field_variable: f.field_variable || null,
|
||||
font_size: f.font_size || null,
|
||||
text_align: f.text_align || 'L',
|
||||
is_required: f.is_required !== false,
|
||||
})),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user