From 1ccf97ce6b28efc1e2fe42ad56524f446b448e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Fri, 6 Mar 2026 22:25:00 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[approval]=20=EC=9C=84=EC=9E=84?= =?UTF-8?q?=EC=9E=A5=20=EC=96=91=EC=8B=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 위임장 전용 폼(_delegation-form.blade.php) 생성 - 위임장 읽기전용 뷰(_delegation-show.blade.php) 생성 - create/edit/show 페이지에 위임장 통합 - 미리보기/인쇄 기능 포함 - 인사/근태 카테고리에 배치 --- resources/views/approvals/create.blade.php | 149 +++++++++++- resources/views/approvals/edit.blade.php | 167 ++++++++++++- .../partials/_delegation-form.blade.php | 171 +++++++++++++ .../partials/_delegation-show.blade.php | 225 ++++++++++++++++++ resources/views/approvals/show.blade.php | 2 + 5 files changed, 709 insertions(+), 5 deletions(-) create mode 100644 resources/views/approvals/partials/_delegation-form.blade.php create mode 100644 resources/views/approvals/partials/_delegation-show.blade.php diff --git a/resources/views/approvals/create.blade.php b/resources/views/approvals/create.blade.php index 921a67ae..34d720a4 100644 --- a/resources/views/approvals/create.blade.php +++ b/resources/views/approvals/create.blade.php @@ -135,6 +135,11 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline- 'tenantInfo' => $tenantInfo ?? [], ]) + {{-- 위임장 전용 폼 --}} + @include('approvals.partials._delegation-form', [ + 'tenantInfo' => $tenantInfo ?? [], + ]) + {{-- 지출결의서 전용 폼 --}} @include('approvals.partials._expense-form', [ 'initialData' => [], @@ -314,6 +319,11 @@ class="p-1 text-gray-400 hover:text-gray-600 transition"> color: 'border-rose-200 bg-rose-50', titleColor: 'text-rose-800', textColor: 'text-rose-600', text: '법인인감, 사용인감 등 회사 인감의 사용을 신청하는 문서입니다. 인감 종류, 용도, 제출처를 기재하며, 승인 후 인감을 사용할 수 있습니다.', }, + delegation: { + title: '위임장', icon: '📜', + color: 'border-amber-200 bg-amber-50', titleColor: 'text-amber-800', textColor: 'text-amber-600', + text: '법인의 업무를 대리인에게 위임하는 문서입니다. 위임인(회사), 수임인(대리인), 위임사항, 위임기간을 기재하며, 승인 후 위임장으로 사용할 수 있습니다.', + }, pr_expense: { title: '지출품의서', icon: '📋', color: 'border-orange-200 bg-orange-50', titleColor: 'text-orange-800', textColor: 'text-orange-700', @@ -344,7 +354,7 @@ class="p-1 text-gray-400 hover:text-gray-600 transition"> // 2단계 분류 정의 (코드 → 카테고리) const formCategoryMap = { BUSINESS_DRAFT: '일반', - leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', + leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', delegation: '인사/근태', employment_cert: '증명서', career_cert: '증명서', appointment_cert: '증명서', seal_usage: '증명서', pr_expense: '품의', pr_contract: '품의', pr_purchase: '품의', pr_trip: '품의', pr_settlement: '품의', expense: '재무', @@ -432,6 +442,7 @@ function updateFormDescription(formId) { let isAppointmentCertForm = false; let isResignationForm = false; let isSealUsageForm = false; +let isDelegationForm = false; // 양식코드별 표시할 유형 목록 const leaveTypesByFormCode = { @@ -681,6 +692,7 @@ function switchFormMode(formId) { const appointmentCertContainer = document.getElementById('appointment-cert-form-container'); const resignationContainer = document.getElementById('resignation-form-container'); const sealUsageContainer = document.getElementById('seal-usage-form-container'); + const delegationContainer = document.getElementById('delegation-form-container'); const bodyArea = document.getElementById('body-area'); const expenseLoadBtn = document.getElementById('expense-load-btn'); @@ -695,6 +707,7 @@ function switchFormMode(formId) { appointmentCertContainer.style.display = 'none'; resignationContainer.style.display = 'none'; sealUsageContainer.style.display = 'none'; + delegationContainer.style.display = 'none'; expenseLoadBtn.style.display = 'none'; bodyArea.style.display = 'none'; isExpenseForm = false; @@ -705,6 +718,7 @@ function switchFormMode(formId) { isAppointmentCertForm = false; isResignationForm = false; isSealUsageForm = false; + isDelegationForm = false; if (code === 'expense') { isExpenseForm = true; @@ -767,6 +781,9 @@ function switchFormMode(formId) { } else if (code === 'seal_usage') { isSealUsageForm = true; sealUsageContainer.style.display = ''; + } else if (code === 'delegation') { + isDelegationForm = true; + delegationContainer.style.display = ''; } else { bodyArea.style.display = ''; } @@ -777,7 +794,7 @@ function applyBodyTemplate(formId) { switchFormMode(formId); // 전용 폼이면 제목을 양식명으로 설정하고 body template 적용 건너뜀 - if (isExpenseForm || isPurchaseRequestForm || isLeaveForm || isCertForm || isCareerCertForm || isAppointmentCertForm || isResignationForm || isSealUsageForm) { + if (isExpenseForm || isPurchaseRequestForm || isLeaveForm || isCertForm || isCareerCertForm || isAppointmentCertForm || isResignationForm || isSealUsageForm || isDelegationForm) { const titleEl = document.getElementById('title'); const formSelect = document.getElementById('form_id'); titleEl.value = formSelect.options[formSelect.selectedIndex].text; @@ -994,6 +1011,29 @@ function applyBodyTemplate(formId) { company_address: document.getElementById('su-company-address').value, }; formBody = null; + } else if (isDelegationForm) { + const dlAgentName = document.getElementById('dl-agent-name').value.trim(); + if (!dlAgentName) { + showToast('수임인(대리인) 성명을 입력해주세요.', 'warning'); + return; + } + + formContent = { + company_name: document.getElementById('dl-company-name').value, + business_num: document.getElementById('dl-business-num').value, + ceo_name: document.getElementById('dl-ceo-name').value, + company_address: document.getElementById('dl-company-address').value, + agent_name: dlAgentName, + agent_birth: document.getElementById('dl-agent-birth').value, + agent_address: document.getElementById('dl-agent-address').value.trim(), + agent_phone: document.getElementById('dl-agent-phone').value.trim(), + agent_department: document.getElementById('dl-agent-department').value.trim(), + delegation_detail: document.getElementById('dl-delegation-detail').value.trim(), + period_start: document.getElementById('dl-period-start').value, + period_end: document.getElementById('dl-period-end').value, + attachments_desc: document.getElementById('dl-attachments-desc').value.trim(), + }; + formBody = null; } else if (isResignationForm) { const reason = getResignationReason(); if (!reason) { @@ -1919,5 +1959,110 @@ function printSealUsagePreview() { win.document.close(); win.print(); } + +// ── 위임장 미리보기 ── +function buildDelegationPreviewHtml(data) { + return ` +
+

위 임 장

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
위임인법인명${data.company_name || ''}
사업자등록번호${data.business_num || ''}
주소${data.company_address || ''}
대표자${data.ceo_name || ''}
수임인성명${data.agent_name || ''}
생년월일${data.agent_birth || ''}
주소${data.agent_address || ''}
연락처${data.agent_phone || ''}
소속${data.agent_department || ''}
+
+
위임사항
+
${data.delegation_detail || ''}
+
+
+
위임기간
+
${data.period_start || ''} ~ ${data.period_end || ''}
+
+ ${data.attachments_desc ? ` +
+
첨부서류
+
${data.attachments_desc}
+
` : ''} +
+

위와 같이 권한을 위임합니다.

+

${data.company_name || ''}

+

대표이사 ${data.ceo_name || ''} (인)

+
+ `; +} + +function openDelegationPreview() { + const data = { + company_name: document.getElementById('dl-company-name').value, + business_num: document.getElementById('dl-business-num').value, + ceo_name: document.getElementById('dl-ceo-name').value, + company_address: document.getElementById('dl-company-address').value, + agent_name: document.getElementById('dl-agent-name').value, + agent_birth: document.getElementById('dl-agent-birth').value, + agent_address: document.getElementById('dl-agent-address').value, + agent_phone: document.getElementById('dl-agent-phone').value, + agent_department: document.getElementById('dl-agent-department').value, + delegation_detail: document.getElementById('dl-delegation-detail').value, + period_start: document.getElementById('dl-period-start').value, + period_end: document.getElementById('dl-period-end').value, + attachments_desc: document.getElementById('dl-attachments-desc').value, + }; + document.getElementById('delegation-preview-content').innerHTML = buildDelegationPreviewHtml(data); + document.getElementById('delegation-preview-modal').style.display = ''; + document.body.style.overflow = 'hidden'; +} + +function closeDelegationPreview() { + document.getElementById('delegation-preview-modal').style.display = 'none'; + document.body.style.overflow = ''; +} + +function printDelegationPreview() { + const content = document.getElementById('delegation-preview-content').innerHTML; + const win = window.open('', '_blank'); + win.document.write('위임장'); + win.document.write(''); + win.document.write(''); + win.document.write(content); + win.document.write(''); + win.document.close(); + win.print(); +} @endpush diff --git a/resources/views/approvals/edit.blade.php b/resources/views/approvals/edit.blade.php index 6b0cf394..afa7e8d4 100644 --- a/resources/views/approvals/edit.blade.php +++ b/resources/views/approvals/edit.blade.php @@ -153,6 +153,11 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline- 'tenantInfo' => $tenantInfo ?? [], ]) + {{-- 위임장 전용 폼 --}} + @include('approvals.partials._delegation-form', [ + 'tenantInfo' => $tenantInfo ?? [], + ]) + {{-- 지출결의서 전용 폼 --}} @php $existingFiles = []; @@ -290,6 +295,7 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon let isPurchaseRequestForm = false; let isCertForm = false; let isSealUsageForm = false; +let isDelegationForm = false; const formDescriptions = { BUSINESS_DRAFT: { @@ -342,6 +348,11 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon color: 'border-rose-200 bg-rose-50', titleColor: 'text-rose-800', textColor: 'text-rose-600', text: '법인인감, 사용인감 등 회사 인감의 사용을 신청하는 문서입니다. 인감 종류, 용도, 제출처를 기재하며, 승인 후 인감을 사용할 수 있습니다.', }, + delegation: { + title: '위임장', icon: '📜', + color: 'border-amber-200 bg-amber-50', titleColor: 'text-amber-800', textColor: 'text-amber-600', + text: '법인의 업무를 대리인에게 위임하는 문서입니다. 위임인(회사), 수임인(대리인), 위임사항, 위임기간을 기재하며, 승인 후 위임장으로 사용할 수 있습니다.', + }, pr_expense: { title: '지출품의서', icon: '📋', color: 'border-orange-200 bg-orange-50', titleColor: 'text-orange-800', textColor: 'text-orange-700', @@ -372,7 +383,7 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon // 2단계 분류 정의 const formCategoryMap = { BUSINESS_DRAFT: '일반', - leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', + leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', delegation: '인사/근태', employment_cert: '증명서', career_cert: '증명서', appointment_cert: '증명서', seal_usage: '증명서', pr_expense: '품의', pr_contract: '품의', pr_purchase: '품의', pr_trip: '품의', pr_settlement: '품의', expense: '재무', @@ -613,17 +624,20 @@ function switchFormMode(formId) { const purchaseRequestContainer = document.getElementById('purchase-request-form-container'); const certContainer = document.getElementById('cert-form-container'); const sealUsageContainer = document.getElementById('seal-usage-form-container'); + const delegationContainer = document.getElementById('delegation-form-container'); const bodyArea = document.getElementById('body-area'); expenseContainer.style.display = 'none'; purchaseRequestContainer.style.display = 'none'; certContainer.style.display = 'none'; sealUsageContainer.style.display = 'none'; + delegationContainer.style.display = 'none'; bodyArea.style.display = 'none'; isExpenseForm = false; isPurchaseRequestForm = false; isCertForm = false; isSealUsageForm = false; + isDelegationForm = false; if (code === 'expense') { isExpenseForm = true; @@ -643,6 +657,9 @@ function switchFormMode(formId) { } else if (code === 'seal_usage') { isSealUsageForm = true; sealUsageContainer.style.display = ''; + } else if (code === 'delegation') { + isDelegationForm = true; + delegationContainer.style.display = ''; } else { bodyArea.style.display = ''; } @@ -653,7 +670,7 @@ function applyBodyTemplate(formId) { switchFormMode(formId); // 전용 폼이면 제목만 자동 설정하고 body template 적용 건너뜀 - if (isExpenseForm || isPurchaseRequestForm || isCertForm || isSealUsageForm) { + if (isExpenseForm || isPurchaseRequestForm || isCertForm || isSealUsageForm || isDelegationForm) { const titleEl = document.getElementById('title'); if (!titleEl.value.trim()) { const formSelect = document.getElementById('form_id'); @@ -759,8 +776,24 @@ function applyBodyTemplate(formId) { } } + // 위임장 기존 데이터 복원 + if (isDelegationForm) { + const dlContent = @json($approval->content ?? []); + if (dlContent.agent_name) { + document.getElementById('dl-agent-name').value = dlContent.agent_name || ''; + document.getElementById('dl-agent-birth').value = dlContent.agent_birth || ''; + document.getElementById('dl-agent-address').value = dlContent.agent_address || ''; + document.getElementById('dl-agent-phone').value = dlContent.agent_phone || ''; + document.getElementById('dl-agent-department').value = dlContent.agent_department || ''; + document.getElementById('dl-delegation-detail').value = dlContent.delegation_detail || ''; + document.getElementById('dl-period-start').value = dlContent.period_start || ''; + document.getElementById('dl-period-end').value = dlContent.period_end || ''; + document.getElementById('dl-attachments-desc').value = dlContent.attachments_desc || ''; + } + } + // 전용 폼이 아닌 경우에만 Quill 편집기 자동 활성화 - if (!isExpenseForm && !isPurchaseRequestForm && !isCertForm && !isSealUsageForm) { + if (!isExpenseForm && !isPurchaseRequestForm && !isCertForm && !isSealUsageForm && !isDelegationForm) { const existingBody = document.getElementById('body').value; if (/<[a-z][\s\S]*>/i.test(existingBody)) { document.getElementById('useEditor').checked = true; @@ -857,6 +890,29 @@ function applyBodyTemplate(formId) { company_address: document.getElementById('su-company-address').value, }; formBody = null; + } else if (isDelegationForm) { + const dlAgentName = document.getElementById('dl-agent-name').value.trim(); + if (!dlAgentName) { + showToast('수임인(대리인) 성명을 입력해주세요.', 'warning'); + return; + } + + formContent = { + company_name: document.getElementById('dl-company-name').value, + business_num: document.getElementById('dl-business-num').value, + ceo_name: document.getElementById('dl-ceo-name').value, + company_address: document.getElementById('dl-company-address').value, + agent_name: dlAgentName, + agent_birth: document.getElementById('dl-agent-birth').value, + agent_address: document.getElementById('dl-agent-address').value.trim(), + agent_phone: document.getElementById('dl-agent-phone').value.trim(), + agent_department: document.getElementById('dl-agent-department').value.trim(), + delegation_detail: document.getElementById('dl-delegation-detail').value.trim(), + period_start: document.getElementById('dl-period-start').value, + period_end: document.getElementById('dl-period-end').value, + attachments_desc: document.getElementById('dl-attachments-desc').value.trim(), + }; + formBody = null; } const payload = { @@ -1189,5 +1245,110 @@ function printSealUsagePreview() { win.document.close(); win.print(); } + +// ── 위임장 미리보기 ── +function buildDelegationPreviewHtml(data) { + return ` +
+

위 임 장

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
위임인법인명${data.company_name || ''}
사업자등록번호${data.business_num || ''}
주소${data.company_address || ''}
대표자${data.ceo_name || ''}
수임인성명${data.agent_name || ''}
생년월일${data.agent_birth || ''}
주소${data.agent_address || ''}
연락처${data.agent_phone || ''}
소속${data.agent_department || ''}
+
+
위임사항
+
${data.delegation_detail || ''}
+
+
+
위임기간
+
${data.period_start || ''} ~ ${data.period_end || ''}
+
+ ${data.attachments_desc ? ` +
+
첨부서류
+
${data.attachments_desc}
+
` : ''} +
+

위와 같이 권한을 위임합니다.

+

${data.company_name || ''}

+

대표이사 ${data.ceo_name || ''} (인)

+
+ `; +} + +function openDelegationPreview() { + const data = { + company_name: document.getElementById('dl-company-name').value, + business_num: document.getElementById('dl-business-num').value, + ceo_name: document.getElementById('dl-ceo-name').value, + company_address: document.getElementById('dl-company-address').value, + agent_name: document.getElementById('dl-agent-name').value, + agent_birth: document.getElementById('dl-agent-birth').value, + agent_address: document.getElementById('dl-agent-address').value, + agent_phone: document.getElementById('dl-agent-phone').value, + agent_department: document.getElementById('dl-agent-department').value, + delegation_detail: document.getElementById('dl-delegation-detail').value, + period_start: document.getElementById('dl-period-start').value, + period_end: document.getElementById('dl-period-end').value, + attachments_desc: document.getElementById('dl-attachments-desc').value, + }; + document.getElementById('delegation-preview-content').innerHTML = buildDelegationPreviewHtml(data); + document.getElementById('delegation-preview-modal').style.display = ''; + document.body.style.overflow = 'hidden'; +} + +function closeDelegationPreview() { + document.getElementById('delegation-preview-modal').style.display = 'none'; + document.body.style.overflow = ''; +} + +function printDelegationPreview() { + const content = document.getElementById('delegation-preview-content').innerHTML; + const win = window.open('', '_blank'); + win.document.write('위임장'); + win.document.write(''); + win.document.write(''); + win.document.write(content); + win.document.write(''); + win.document.close(); + win.print(); +} @endpush diff --git a/resources/views/approvals/partials/_delegation-form.blade.php b/resources/views/approvals/partials/_delegation-form.blade.php new file mode 100644 index 00000000..3e3d3936 --- /dev/null +++ b/resources/views/approvals/partials/_delegation-form.blade.php @@ -0,0 +1,171 @@ +{{-- + 위임장 전용 폼 + Props: + $tenantInfo (array) - 테넌트(회사) 정보 +--}} +@php + $tenantInfo = $tenantInfo ?? []; +@endphp + + + +{{-- 위임장 미리보기 모달 --}} + diff --git a/resources/views/approvals/partials/_delegation-show.blade.php b/resources/views/approvals/partials/_delegation-show.blade.php new file mode 100644 index 00000000..13d8806d --- /dev/null +++ b/resources/views/approvals/partials/_delegation-show.blade.php @@ -0,0 +1,225 @@ +{{-- + 위임장 읽기전용 렌더링 + Props: + $content (array) - approvals.content JSON +--}} +
+ {{-- 미리보기 버튼 --}} +
+ +
+ + {{-- 1. 위임인 --}} +
+
+

1. 위임인

+
+
+
+
+ 법인명 +
{{ $content['company_name'] ?? '-' }}
+
+
+ 사업자등록번호 +
{{ $content['business_num'] ?? '-' }}
+
+
+ 주소 +
{{ $content['company_address'] ?? '-' }}
+
+
+ 대표자 +
{{ $content['ceo_name'] ?? '-' }}
+
+
+
+
+ + {{-- 2. 수임인 (대리인) --}} +
+
+

2. 수임인 (대리인)

+
+
+
+
+ 성명 +
{{ $content['agent_name'] ?? '-' }}
+
+
+ 생년월일 +
{{ $content['agent_birth'] ?? '-' }}
+
+
+ 주소 +
{{ $content['agent_address'] ?? '-' }}
+
+
+ 연락처 +
{{ $content['agent_phone'] ?? '-' }}
+
+
+ 소속 +
{{ $content['agent_department'] ?? '-' }}
+
+
+
+
+ + {{-- 3. 위임사항 --}} +
+
+

3. 위임사항

+
+
+
{{ $content['delegation_detail'] ?? '-' }}
+
+
+ + {{-- 4. 위임기간 --}} +
+
+

4. 위임기간

+
+
+
{{ $content['period_start'] ?? '-' }} ~ {{ $content['period_end'] ?? '-' }}
+
+
+ + {{-- 5. 첨부서류 --}} + @if(!empty($content['attachments_desc'])) +
+
+

5. 첨부서류

+
+
+
{{ $content['attachments_desc'] }}
+
+
+ @endif +
+ +{{-- 미리보기 모달 --}} + + +@push('scripts') + +@endpush diff --git a/resources/views/approvals/show.blade.php b/resources/views/approvals/show.blade.php index b64939d4..6c5da660 100644 --- a/resources/views/approvals/show.blade.php +++ b/resources/views/approvals/show.blade.php @@ -115,6 +115,8 @@ class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition @include('approvals.partials._resignation-show', ['content' => $approval->content]) @elseif(!empty($approval->content) && $approval->form?->code === 'seal_usage') @include('approvals.partials._seal-usage-show', ['content' => $approval->content]) + @elseif(!empty($approval->content) && $approval->form?->code === 'delegation') + @include('approvals.partials._delegation-show', ['content' => $approval->content]) @elseif($approval->body && preg_match('/<[a-z][\s\S]*>/i', $approval->body))
{!! strip_tags($approval->body, '