From 406d47bc938bff44beeb05a1fb105f0364ce396a 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 11:40:50 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[approvals]=20=ED=92=88=EC=9D=98?= =?UTF-8?q?=EC=84=9C=205=EC=A2=85=20=EB=B6=84=EB=A6=AC=20(=EC=A7=80?= =?UTF-8?q?=EC=B6=9C/=EA=B3=84=EC=95=BD=EC=B2=B4=EA=B2=B0/=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4/=EC=B6=9C=EC=9E=A5/=EB=B9=84=EC=9A=A9=EC=A0=95?= =?UTF-8?q?=EC=82=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 단일 품의서(purchase_request)를 5가지 전문 양식으로 분리 - pr_expense: 지출품의서 (항목/금액/비고) - pr_contract: 계약체결품의서 (계약상대방/기간/금액/조건) - pr_purchase: 구매품의서 (품목/수량/단가/납품정보) - pr_trip: 출장품의서 (일정표/경비내역) - pr_settlement: 비용정산품의서 (사용일자/항목/지급방법) - Alpine.js 단일 컴포넌트로 5종 동적 전환 - show/create/edit 모두 pr_ prefix 코드 자동 감지 --- resources/views/approvals/create.blade.php | 8 +- resources/views/approvals/edit.blade.php | 6 +- .../partials/_purchase-request-form.blade.php | 804 ++++++++++++++---- .../partials/_purchase-request-show.blade.php | 369 ++++++-- resources/views/approvals/show.blade.php | 2 +- 5 files changed, 964 insertions(+), 225 deletions(-) diff --git a/resources/views/approvals/create.blade.php b/resources/views/approvals/create.blade.php index eb11aa99..b95782d1 100644 --- a/resources/views/approvals/create.blade.php +++ b/resources/views/approvals/create.blade.php @@ -521,9 +521,13 @@ function switchFormMode(formId) { isExpenseForm = true; expenseContainer.style.display = ''; expenseLoadBtn.style.display = ''; - } else if (code === 'purchase_request') { + } else if (code && code.startsWith('pr_')) { isPurchaseRequestForm = true; purchaseRequestContainer.style.display = ''; + setTimeout(() => { + const prData = purchaseRequestContainer._x_dataStack?.[0]; + if (prData) prData.setPrType(code); + }, 50); } else if (leaveFormCodes.includes(code)) { isLeaveForm = true; leaveContainer.style.display = ''; @@ -564,7 +568,7 @@ function applyBodyTemplate(formId) { switchFormMode(formId); // 전용 폼이면 제목을 양식명으로 설정하고 body template 적용 건너뜀 - if (isExpenseForm || isLeaveForm || isCertForm || isCareerCertForm || isAppointmentCertForm || isResignationForm) { + if (isExpenseForm || isPurchaseRequestForm || isLeaveForm || isCertForm || isCareerCertForm || isAppointmentCertForm || isResignationForm) { const titleEl = document.getElementById('title'); const formSelect = document.getElementById('form_id'); titleEl.value = formSelect.options[formSelect.selectedIndex].text; diff --git a/resources/views/approvals/edit.blade.php b/resources/views/approvals/edit.blade.php index 15d3865e..a71efc7e 100644 --- a/resources/views/approvals/edit.blade.php +++ b/resources/views/approvals/edit.blade.php @@ -446,9 +446,13 @@ function switchFormMode(formId) { if (code === 'expense') { isExpenseForm = true; expenseContainer.style.display = ''; - } else if (code === 'purchase_request') { + } else if (code && code.startsWith('pr_')) { isPurchaseRequestForm = true; purchaseRequestContainer.style.display = ''; + setTimeout(() => { + const prData = purchaseRequestContainer._x_dataStack?.[0]; + if (prData) prData.setPrType(code); + }, 50); } else if (code === 'employment_cert') { isCertForm = true; certContainer.style.display = ''; diff --git a/resources/views/approvals/partials/_purchase-request-form.blade.php b/resources/views/approvals/partials/_purchase-request-form.blade.php index 8fb08bda..13d2ffaf 100644 --- a/resources/views/approvals/partials/_purchase-request-form.blade.php +++ b/resources/views/approvals/partials/_purchase-request-form.blade.php @@ -1,6 +1,6 @@ {{-- - 품의서 전용 폼 (Alpine.js) - 지출결의서에서 지출방법(카드/계좌) 관련 필드를 제거하고, 구매 목적 필드를 추가한 양식. + 품의서 5종 통합 폼 (Alpine.js) + Types: pr_expense(지출), pr_contract(계약체결), pr_purchase(구매), pr_trip(출장), pr_settlement(비용정산) Props: $initialData (array|null) - 기존 content JSON (edit 시) $initialFiles (array|null) - 기존 첨부파일 [{id, name, size, mime_type}] (edit 시) @@ -15,25 +15,22 @@ x-data="purchaseRequestForm({{ json_encode($initialData) }}, '{{ $userName }}', {{ json_encode($initialFiles) }})" x-cloak> - {{-- 구매 목적 --}} -
- - -
+ {{-- 품의서 유형 표시 --}} +
- {{-- 기본 정보 --}} -
+ {{-- 공통: 기본 정보 --}} +
-
- - -
- -
- - - - - - - - - - - - - - - - - - - - - - -
품명수량단가금액업체명비고
합계 - -
-
-
+ {{-- ═══════════════════════════════════════════ --}} + {{-- 지출품의서 (pr_expense) --}} + {{-- ═══════════════════════════════════════════ --}} + + + {{-- ═══════════════════════════════════════════ --}} + {{-- 계약체결품의서 (pr_contract) --}} + {{-- ═══════════════════════════════════════════ --}} + + + {{-- ═══════════════════════════════════════════ --}} + {{-- 구매품의서 (pr_purchase) --}} + {{-- ═══════════════════════════════════════════ --}} + + + {{-- ═══════════════════════════════════════════ --}} + {{-- 출장품의서 (pr_trip) --}} + {{-- ═══════════════════════════════════════════ --}} + + + {{-- ═══════════════════════════════════════════ --}} + {{-- 비용정산품의서 (pr_settlement) --}} + {{-- ═══════════════════════════════════════════ --}} + + + {{-- 공통: 첨부서류 메모 --}}
- {{-- 첨부파일 업로드 --}} + {{-- 공통: 첨부파일 업로드 --}}
-
0) ? initialData.items.map(makeItem) : [makeItem({})]; + const schedules = (initialData?.schedules && initialData.schedules.length > 0) + ? initialData.schedules.map(makeSchedule) + : [makeSchedule({})]; + const today = new Date().toISOString().slice(0, 10); return { + prType: initialData?.pr_type || 'pr_purchase', + + prTypeLabels: { + pr_expense: '지출품의서', + pr_contract: '계약체결품의서', + pr_purchase: '구매품의서', + pr_trip: '출장품의서', + pr_settlement: '비용정산품의서', + }, + formData: { - purpose: initialData?.purpose || '', write_date: initialData?.write_date || today, - desired_date: initialData?.desired_date || '', department: initialData?.department || '', writer_name: initialData?.writer_name || authUserName, - items: items, + purpose: initialData?.purpose || '', attachment_memo: initialData?.attachment_memo || '', + + // pr_expense + expense_category: initialData?.expense_category || '', + usage_date: initialData?.usage_date || '', + + // pr_contract + contract_party: initialData?.contract_party || '', + contract_party_id: initialData?.contract_party_id || null, + contract_party_biz_no: initialData?.contract_party_biz_no || '', + contract_content: initialData?.contract_content || '', + contract_start: initialData?.contract_start || '', + contract_end: initialData?.contract_end || '', + contract_amount: parseInt(initialData?.contract_amount) || 0, + contract_scope: initialData?.contract_scope || '', + delivery_service: initialData?.delivery_service || '', + payment_terms: initialData?.payment_terms || '', + special_terms: initialData?.special_terms || '', + + // pr_purchase + vendor: initialData?.vendor || '', + vendor_id: initialData?.vendor_id || null, + vendor_biz_no: initialData?.vendor_biz_no || '', + delivery_date: initialData?.delivery_date || '', + delivery_place: initialData?.delivery_place || '', + + // pr_trip + traveler: initialData?.traveler || authUserName, + destination: initialData?.destination || '', + trip_start: initialData?.trip_start || '', + trip_end: initialData?.trip_end || '', + schedules: schedules, + trip_expenses: { + transport: parseInt(initialData?.trip_expenses?.transport) || 0, + accommodation: parseInt(initialData?.trip_expenses?.accommodation) || 0, + meal: parseInt(initialData?.trip_expenses?.meal) || 0, + other: parseInt(initialData?.trip_expenses?.other) || 0, + }, + + // pr_settlement + payment_method: initialData?.payment_method || '', + + // shared items + items: items, }, isDragging: false, @@ -237,6 +677,20 @@ function makeItem(data) { return this.formData.items.reduce((sum, item) => sum + (parseInt(item.amount) || 0), 0); }, + get tripExpenseTotal() { + const e = this.formData.trip_expenses; + return (parseInt(e.transport) || 0) + (parseInt(e.accommodation) || 0) + (parseInt(e.meal) || 0) + (parseInt(e.other) || 0); + }, + + setPrType(type) { + this.prType = type; + // 타입 변경 시 items/schedules 초기화 (신규 작성 시만) + if (!initialData?.pr_type) { + this.formData.items = [makeItem({})]; + this.formData.schedules = [makeSchedule({})]; + } + }, + addItem() { this.formData.items.push(makeItem({})); }, @@ -247,6 +701,16 @@ function makeItem(data) { } }, + addSchedule() { + this.formData.schedules.push(makeSchedule({})); + }, + + removeSchedule(index) { + if (this.formData.schedules.length > 1) { + this.formData.schedules.splice(index, 1); + } + }, + recalcAmount(item) { const qty = parseInt(item.quantity) || 0; const price = parseInt(item.unit_price) || 0; @@ -272,25 +736,19 @@ function makeItem(data) { }, handleDrop(event) { - const files = Array.from(event.dataTransfer.files); - this.uploadFiles(files); + this.uploadFiles(Array.from(event.dataTransfer.files)); }, handleFileSelect(event) { - const files = Array.from(event.target.files); - this.uploadFiles(files); + this.uploadFiles(Array.from(event.target.files)); event.target.value = ''; }, async uploadFiles(files) { if (this.fileUploading || files.length === 0) return; - const maxSize = 20 * 1024 * 1024; const valid = files.filter(f => { - if (f.size > maxSize) { - showToast(`${f.name}: 20MB를 초과합니다.`, 'warning'); - return false; - } + if (f.size > maxSize) { showToast(`${f.name}: 20MB를 초과합니다.`, 'warning'); return false; } return true; }); if (valid.length === 0) return; @@ -298,7 +756,6 @@ function makeItem(data) { this.fileUploading = true; this.fileProgress = 0; let completed = 0; - for (const file of valid) { this.fileUploadStatus = `${completed + 1}/${valid.length} 업로드 중...`; try { @@ -307,11 +764,8 @@ function makeItem(data) { }); this.uploadedFiles.push(result); completed++; - } catch (e) { - showToast(`${file.name} 업로드 실패`, 'error'); - } + } catch (e) { showToast(`${file.name} 업로드 실패`, 'error'); } } - this.fileProgress = 100; this.fileUploadStatus = '완료'; setTimeout(() => { this.fileUploading = false; this.fileProgress = 0; }, 800); @@ -319,9 +773,8 @@ function makeItem(data) { uploadSingle(file, onProgress) { return new Promise((resolve, reject) => { - const formData = new FormData(); - formData.append('file', file); - + const fd = new FormData(); + fd.append('file', file); const xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) onProgress(Math.round(e.loaded / e.total * 100)); @@ -329,15 +782,14 @@ function makeItem(data) { xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { const res = JSON.parse(xhr.responseText); - if (res.success) resolve(res.data); - else reject(new Error(res.message)); + if (res.success) resolve(res.data); else reject(new Error(res.message)); } else { reject(new Error('업로드 실패')); } }; xhr.onerror = () => reject(new Error('네트워크 오류')); xhr.open('POST', '/api/admin/approvals/upload-file'); xhr.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').content); xhr.setRequestHeader('Accept', 'application/json'); - xhr.send(formData); + xhr.send(fd); }); }, @@ -346,35 +798,47 @@ function makeItem(data) { try { await fetch(`/api/admin/approvals/files/${file.id}`, { method: 'DELETE', - headers: { - 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, - 'Accept': 'application/json', - }, + headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, 'Accept': 'application/json' }, }); - } catch (e) { /* 삭제 실패해도 목록에서는 제거 */ } + } catch (e) {} this.uploadedFiles.splice(index, 1); }, getFormData() { - return { - purpose: this.formData.purpose, + const base = { + pr_type: this.prType, write_date: this.formData.write_date, - desired_date: this.formData.desired_date, department: this.formData.department, writer_name: this.formData.writer_name, - items: this.formData.items.map(item => ({ - description: item.description, - quantity: parseInt(item.quantity) || 0, - unit_price: parseInt(item.unit_price) || 0, - amount: parseInt(item.amount) || 0, - vendor: item.vendor, - vendor_id: item.vendor_id || null, - vendor_biz_no: item.vendor_biz_no || '', - remark: item.remark, - })), - total_amount: this.totalAmount, + purpose: this.formData.purpose, attachment_memo: this.formData.attachment_memo, }; + + const cleanItems = () => this.formData.items.map(item => ({ + date: item.date || '', + description: item.description, + quantity: parseInt(item.quantity) || 0, + unit_price: parseInt(item.unit_price) || 0, + amount: parseInt(item.amount) || 0, + remark: item.remark, + })); + + if (this.prType === 'pr_expense') { + return { ...base, expense_category: this.formData.expense_category, usage_date: this.formData.usage_date, items: cleanItems(), total_amount: this.totalAmount }; + } + if (this.prType === 'pr_contract') { + return { ...base, contract_party: this.formData.contract_party, contract_party_id: this.formData.contract_party_id, contract_party_biz_no: this.formData.contract_party_biz_no, contract_content: this.formData.contract_content, contract_start: this.formData.contract_start, contract_end: this.formData.contract_end, contract_amount: parseInt(this.formData.contract_amount) || 0, contract_scope: this.formData.contract_scope, delivery_service: this.formData.delivery_service, payment_terms: this.formData.payment_terms, special_terms: this.formData.special_terms }; + } + if (this.prType === 'pr_purchase') { + return { ...base, items: cleanItems(), total_amount: this.totalAmount, vendor: this.formData.vendor, vendor_id: this.formData.vendor_id, vendor_biz_no: this.formData.vendor_biz_no, delivery_date: this.formData.delivery_date, delivery_place: this.formData.delivery_place }; + } + if (this.prType === 'pr_trip') { + return { ...base, traveler: this.formData.traveler, destination: this.formData.destination, trip_start: this.formData.trip_start, trip_end: this.formData.trip_end, schedules: this.formData.schedules.map(s => ({ date: s.date, destination: s.destination, task: s.task })), trip_expenses: { ...this.formData.trip_expenses }, trip_expense_total: this.tripExpenseTotal }; + } + if (this.prType === 'pr_settlement') { + return { ...base, items: cleanItems(), total_amount: this.totalAmount, payment_method: this.formData.payment_method }; + } + return base; }, getFileIds() { @@ -383,7 +847,7 @@ function makeItem(data) { }; } -function prVendorSearch(item) { +function prVendorSearch(target, fieldName) { let debounceTimer = null; let dropdown = null; let results = []; @@ -399,7 +863,6 @@ function removeDropdown() { function renderDropdown(inputEl) { if (!dropdown) { dropdown = document.createElement('div'); - dropdown.className = 'pr-vendor-search-dropdown'; dropdown.style.cssText = 'position:fixed;z-index:99999;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 10px 25px rgba(0,0,0,0.15);max-height:220px;overflow-y:auto;min-width:220px;'; document.body.appendChild(dropdown); } @@ -414,13 +877,13 @@ function renderDropdown(inputEl) { } dropdown.innerHTML = results.map((p, i) => { const bg = i === highlighted ? 'background:#eff6ff;' : ''; - const bizInfo = [p.biz_no, p.ceo ? '대표: ' + p.ceo : ''].filter(Boolean).join(' '); const d = document.createElement('div'); d.textContent = p.name; const safeName = d.innerHTML; + const bizInfo = [p.biz_no, p.ceo ? '대표: ' + p.ceo : ''].filter(Boolean).join(' '); d.textContent = bizInfo; const safeBiz = d.innerHTML; - return `
+ return `
${safeName}
${safeBiz ? '
' + safeBiz + '
' : ''}
`; @@ -431,9 +894,9 @@ function renderDropdown(inputEl) { e.preventDefault(); const idx = parseInt(el.dataset.idx); if (results[idx]) { - item.vendor = results[idx].name; - item.vendor_id = results[idx].id; - item.vendor_biz_no = results[idx].biz_no || ''; + target[fieldName] = results[idx].name; + target[fieldName + '_id'] = results[idx].id; + target[fieldName + '_biz_no'] = results[idx].biz_no || ''; inputEl.value = results[idx].name; selected = true; } @@ -446,22 +909,19 @@ function renderDropdown(inputEl) { return { onInput(value) { if (selected) { selected = false; return; } - item.vendor = value; - item.vendor_id = null; - item.vendor_biz_no = ''; + target[fieldName] = value; + target[fieldName + '_id'] = null; + target[fieldName + '_biz_no'] = ''; highlighted = -1; clearTimeout(debounceTimer); if (value.length < 1) { removeDropdown(); return; } debounceTimer = setTimeout(() => this.search(value), 250); }, - onFocus() { if (selected) return; - if (item.vendor && item.vendor.length >= 1 && !dropdown) { - this.search(item.vendor); - } + const val = target[fieldName]; + if (val && val.length >= 1 && !dropdown) this.search(val); }, - async search(keyword) { try { const res = await fetch(`/barobill/tax-invoice/search-partners?keyword=${encodeURIComponent(keyword)}`); @@ -470,9 +930,7 @@ function renderDropdown(inputEl) { renderDropdown(this.$refs.vinput); } catch (e) { results = []; removeDropdown(); } }, - close() { removeDropdown(); }, - moveDown() { if (!dropdown || results.length === 0) return; clearTimeout(debounceTimer); @@ -488,9 +946,9 @@ function renderDropdown(inputEl) { selectHighlighted() { if (highlighted >= 0 && highlighted < results.length) { const p = results[highlighted]; - item.vendor = p.name; - item.vendor_id = p.id; - item.vendor_biz_no = p.biz_no || ''; + target[fieldName] = p.name; + target[fieldName + '_id'] = p.id; + target[fieldName + '_biz_no'] = p.biz_no || ''; this.$refs.vinput.value = p.name; selected = true; removeDropdown(); diff --git a/resources/views/approvals/partials/_purchase-request-show.blade.php b/resources/views/approvals/partials/_purchase-request-show.blade.php index 8ef22eef..a348ae5d 100644 --- a/resources/views/approvals/partials/_purchase-request-show.blade.php +++ b/resources/views/approvals/partials/_purchase-request-show.blade.php @@ -1,16 +1,34 @@ {{-- - 품의서 읽기전용 렌더링 + 품의서 5종 읽기전용 렌더링 Props: - $content (array) - approvals.content JSON + $content (array) - approvals.content JSON (pr_type 포함) --}} +@php + $prType = $content['pr_type'] ?? 'pr_purchase'; + $prTypeLabels = [ + 'pr_expense' => '지출품의서', + 'pr_contract' => '계약체결품의서', + 'pr_purchase' => '구매품의서', + 'pr_trip' => '출장품의서', + 'pr_settlement' => '비용정산품의서', + ]; +@endphp
- {{-- 구매 목적 --}} - @if(!empty($content['purpose'])) -
- 구매 목적 -
{{ $content['purpose'] }}
-
- @endif + + {{-- 유형 뱃지 --}} + @php + $badgeClass = match($prType) { + 'pr_expense' => 'bg-orange-50 text-orange-700 border-orange-200', + 'pr_contract' => 'bg-purple-50 text-purple-700 border-purple-200', + 'pr_purchase' => 'bg-blue-50 text-blue-700 border-blue-200', + 'pr_trip' => 'bg-green-50 text-green-700 border-green-200', + 'pr_settlement' => 'bg-teal-50 text-teal-700 border-teal-200', + default => 'bg-gray-50 text-gray-700 border-gray-200', + }; + @endphp +
+ {{ $prTypeLabels[$prType] ?? '품의서' }} +
{{-- 기본 정보 --}}
@@ -18,10 +36,6 @@ 작성일자
{{ $content['write_date'] ?? '-' }}
-
- 희망 납기일 -
{{ $content['desired_date'] ?? '-' }}
-
요청부서
{{ $content['department'] ?? '-' }}
@@ -32,44 +46,303 @@
- {{-- 내역 테이블 --}} - @if(!empty($content['items'])) -
- - - - - - - - - - - - - @foreach($content['items'] as $item) - - - - - - - + {{-- ═══ 지출품의서 ═══ --}} + @if($prType === 'pr_expense') +
+ @if(!empty($content['expense_category'])) +
+ 지출항목 +
{{ $content['expense_category'] }}
+
+ @endif + @if(!empty($content['usage_date'])) +
+ 사용일자 +
{{ $content['usage_date'] }}
+
+ @endif +
+ @if(!empty($content['purpose'])) +
+ 사용목적 +
{{ $content['purpose'] }}
+
+ @endif + + @if(!empty($content['items'])) +
+
품명수량단가금액업체명비고
{{ $item['description'] ?? '' }}{{ $item['quantity'] ?? '' }}{{ number_format($item['unit_price'] ?? 0) }}{{ number_format($item['amount'] ?? 0) }}{{ $item['vendor'] ?? '' }}{{ $item['remark'] ?? '' }}
+ + + + + - @endforeach - - - - - - - - -
항목금액비고
합계{{ number_format($content['total_amount'] ?? 0) }}
+ + + @foreach($content['items'] as $item) + + {{ $item['description'] ?? '' }} + {{ number_format($item['amount'] ?? 0) }} + {{ $item['remark'] ?? '' }} + + @endforeach + + + + 합계 + {{ number_format($content['total_amount'] ?? 0) }} + + + + +
+ @endif + @endif + + {{-- ═══ 계약체결품의서 ═══ --}} + @if($prType === 'pr_contract') +
+
+ 계약상대방 +
{{ $content['contract_party'] ?? '-' }}
+
+
+ 계약금액 +
{{ number_format($content['contract_amount'] ?? 0) }}원
+
+ @if(!empty($content['contract_start']) || !empty($content['contract_end'])) +
+ 계약기간 +
{{ $content['contract_start'] ?? '' }} ~ {{ $content['contract_end'] ?? '' }}
+
+ @endif +
+ + @if(!empty($content['contract_content'])) +
+ 계약내용 +
{{ $content['contract_content'] }}
+
+ @endif + + @php + $contractSections = [ + ['key' => 'contract_scope', 'label' => '계약범위'], + ['key' => 'delivery_service', 'label' => '납품/서비스 내용'], + ['key' => 'payment_terms', 'label' => '대금지급조건'], + ['key' => 'special_terms', 'label' => '기타 특약사항'], + ]; + @endphp + @foreach($contractSections as $sec) + @if(!empty($content[$sec['key']])) +
+ {{ $sec['label'] }} +
{{ $content[$sec['key']] }}
+
+ @endif + @endforeach + @endif + + {{-- ═══ 구매품의서 ═══ --}} + @if($prType === 'pr_purchase') + @if(!empty($content['purpose'])) +
+ 구매 목적 +
{{ $content['purpose'] }}
+
+ @endif + + @if(!empty($content['items'])) +
+ + + + + + + + + + + + @foreach($content['items'] as $item) + + + + + + + + @endforeach + + + + + + + + +
품목수량단가금액비고
{{ $item['description'] ?? '' }}{{ $item['quantity'] ?? '' }}{{ number_format($item['unit_price'] ?? 0) }}{{ number_format($item['amount'] ?? 0) }}{{ $item['remark'] ?? '' }}
합계{{ number_format($content['total_amount'] ?? 0) }}
+
+ @endif + +
+ @if(!empty($content['vendor'])) +
+ 납품업체 +
{{ $content['vendor'] }}
+
+ @endif + @if(!empty($content['delivery_date'])) +
+ 납품예정일 +
{{ $content['delivery_date'] }}
+
+ @endif + @if(!empty($content['delivery_place'])) +
+ 납품장소 +
{{ $content['delivery_place'] }}
+
+ @endif
@endif - {{-- 첨부서류 --}} + {{-- ═══ 출장품의서 ═══ --}} + @if($prType === 'pr_trip') +
+
+ 출장자 +
{{ $content['traveler'] ?? '-' }}
+
+
+ 출장지 +
{{ $content['destination'] ?? '-' }}
+
+ @if(!empty($content['trip_start']) || !empty($content['trip_end'])) +
+ 출장기간 +
{{ $content['trip_start'] ?? '' }} ~ {{ $content['trip_end'] ?? '' }}
+
+ @endif +
+ + @if(!empty($content['purpose'])) +
+ 업무내용 +
{{ $content['purpose'] }}
+
+ @endif + + @if(!empty($content['schedules'])) +
+ + + + + + + + + + @foreach($content['schedules'] as $sch) + + + + + + @endforeach + +
일자행선지업무내용
{{ $sch['date'] ?? '' }}{{ $sch['destination'] ?? '' }}{{ $sch['task'] ?? '' }}
+
+ @endif + + @if(!empty($content['trip_expenses'])) + @php $te = $content['trip_expenses']; @endphp +
+
+ 교통비 +
{{ number_format($te['transport'] ?? 0) }}
+
+
+ 숙박비 +
{{ number_format($te['accommodation'] ?? 0) }}
+
+
+ 식비 +
{{ number_format($te['meal'] ?? 0) }}
+
+
+ 기타 +
{{ number_format($te['other'] ?? 0) }}
+
+
+ 합계 +
{{ number_format($content['trip_expense_total'] ?? 0) }}
+
+
+ @endif + @endif + + {{-- ═══ 비용정산품의서 ═══ --}} + @if($prType === 'pr_settlement') + @if(!empty($content['purpose'])) +
+ 정산 사유 +
{{ $content['purpose'] }}
+
+ @endif + + @if(!empty($content['items'])) +
+ + + + + + + + + + + @foreach($content['items'] as $item) + + + + + + + @endforeach + + + + + + + + +
사용일자항목금액비고
{{ $item['date'] ?? '' }}{{ $item['description'] ?? '' }}{{ number_format($item['amount'] ?? 0) }}{{ $item['remark'] ?? '' }}
합계{{ number_format($content['total_amount'] ?? 0) }}
+
+ @endif + + @if(!empty($content['payment_method'])) +
+ 지급방법 +
+ @if($content['payment_method'] === 'corporate_card') + 법인카드 사용 + @elseif($content['payment_method'] === 'personal_advance') + 개인 선지출 후 정산 + @else + {{ $content['payment_method'] }} + @endif +
+
+ @endif + @endif + + {{-- 공통: 첨부서류 메모 --}} @if(!empty($content['attachment_memo']))
첨부서류 @@ -77,7 +350,7 @@
@endif - {{-- 첨부파일 --}} + {{-- 공통: 첨부파일 --}} @if(!empty($approval->attachments))
첨부파일 diff --git a/resources/views/approvals/show.blade.php b/resources/views/approvals/show.blade.php index f84730c5..7c43b15f 100644 --- a/resources/views/approvals/show.blade.php +++ b/resources/views/approvals/show.blade.php @@ -80,7 +80,7 @@ class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition

{{ $approval->title }}

- @if(!empty($approval->content) && $approval->form?->code === 'purchase_request') + @if(!empty($approval->content) && str_starts_with($approval->form?->code ?? '', 'pr_')) @include('approvals.partials._purchase-request-show', ['content' => $approval->content]) @elseif(!empty($approval->content) && $approval->form?->code === 'expense') @include('approvals.partials._expense-show', ['content' => $approval->content])