diff --git a/app/Http/Controllers/Api/Admin/ApprovalApiController.php b/app/Http/Controllers/Api/Admin/ApprovalApiController.php
index c4010be3..639c21b2 100644
--- a/app/Http/Controllers/Api/Admin/ApprovalApiController.php
+++ b/app/Http/Controllers/Api/Admin/ApprovalApiController.php
@@ -444,6 +444,36 @@ public function destroyLine(int $id): JsonResponse
]);
}
+ /**
+ * 지출결의서 이력 (불러오기용)
+ */
+ public function expenseHistory(Request $request): JsonResponse
+ {
+ $tenantId = session('selected_tenant_id');
+
+ $approvals = \App\Models\Approvals\Approval::where('tenant_id', $tenantId)
+ ->where('drafter_id', auth()->id())
+ ->whereHas('form', fn ($q) => $q->where('code', 'expense'))
+ ->whereIn('status', ['draft', 'pending', 'approved', 'rejected', 'cancelled'])
+ ->whereNotNull('content')
+ ->orderByDesc('created_at')
+ ->limit(30)
+ ->get(['id', 'title', 'content', 'status', 'created_at']);
+
+ $data = $approvals->map(fn ($a) => [
+ 'id' => $a->id,
+ 'title' => $a->title,
+ 'status' => $a->status,
+ 'status_label' => $a->status_label,
+ 'total_amount' => $a->content['total_amount'] ?? 0,
+ 'expense_type' => $a->content['expense_type'] ?? '',
+ 'created_at' => $a->created_at->format('Y-m-d'),
+ 'content' => $a->content,
+ ]);
+
+ return response()->json(['success' => true, 'data' => $data]);
+ }
+
/**
* 양식 목록
*/
diff --git a/resources/views/approvals/create.blade.php b/resources/views/approvals/create.blade.php
index 428b58a8..e68b732f 100644
--- a/resources/views/approvals/create.blade.php
+++ b/resources/views/approvals/create.blade.php
@@ -82,6 +82,16 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-
{{-- 지출결의서 전용 폼 --}}
+
+
+
+
@include('approvals.partials._expense-form', [
'initialData' => [],
'cards' => $cards ?? collect(),
@@ -132,6 +142,28 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon
+ {{-- 지출결의서 불러오기 모달 --}}
+
@endsection
@push('styles')
@@ -345,14 +377,17 @@ function switchFormMode(formId) {
const code = formCodes[formId];
const expenseContainer = document.getElementById('expense-form-container');
const bodyArea = document.getElementById('body-area');
+ const expenseLoadArea = document.getElementById('expense-load-area');
if (code === 'expense') {
isExpenseForm = true;
expenseContainer.style.display = '';
+ expenseLoadArea.style.display = '';
bodyArea.style.display = 'none';
} else {
isExpenseForm = false;
expenseContainer.style.display = 'none';
+ expenseLoadArea.style.display = 'none';
bodyArea.style.display = '';
}
}
@@ -401,6 +436,11 @@ function applyBodyTemplate(formId) {
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
+ const expModal = document.getElementById('expense-load-modal');
+ if (expModal && expModal.style.display !== 'none') {
+ closeExpenseLoadModal();
+ return;
+ }
const modal = document.getElementById('approval-line-modal');
if (modal && modal.style.display !== 'none') {
closeApprovalLineModal();
@@ -499,5 +539,129 @@ function applyBodyTemplate(formId) {
showToast('서버 오류가 발생했습니다.', 'error');
}
}
+
+// =========================================================================
+// 지출결의서 불러오기
+// =========================================================================
+
+const expenseTypeLabels = {
+ corporate_card: '법인카드', transfer: '송금', auto_transfer: '자동이체 출금',
+ cash_advance: '현금/가지급정산',
+};
+const statusColors = {
+ draft: 'bg-gray-100 text-gray-600', pending: 'bg-blue-100 text-blue-600',
+ approved: 'bg-green-100 text-green-600', rejected: 'bg-red-100 text-red-600',
+ cancelled: 'bg-yellow-100 text-yellow-600',
+};
+
+async function openExpenseLoadModal() {
+ const modal = document.getElementById('expense-load-modal');
+ const list = document.getElementById('expense-load-list');
+ modal.style.display = '';
+ document.body.style.overflow = 'hidden';
+ list.innerHTML = '불러오는 중...
';
+
+ try {
+ const res = await fetch('/api/admin/approvals/expense-history', {
+ headers: { 'Accept': 'application/json' },
+ });
+ const json = await res.json();
+
+ if (!json.success || !json.data.length) {
+ list.innerHTML = '이전 지출결의서가 없습니다.
';
+ return;
+ }
+
+ list.innerHTML = json.data.map(item => {
+ const amount = parseInt(item.total_amount || 0).toLocaleString('ko-KR');
+ const typeLabel = expenseTypeLabels[item.expense_type] || item.expense_type;
+ const color = statusColors[item.status] || statusColors.draft;
+ return `
+
+
+ ${escapeHtml(item.title)}
+ ${escapeHtml(item.status_label)}
+
+
+ ${escapeHtml(item.created_at)}
+ ${escapeHtml(typeLabel)}
+ ${amount}원
+
+
+
+
`;
+ }).join('');
+ } catch (e) {
+ list.innerHTML = '불러오기 실패
';
+ }
+}
+
+function closeExpenseLoadModal() {
+ document.getElementById('expense-load-modal').style.display = 'none';
+ document.body.style.overflow = '';
+}
+
+async function loadExpenseData(approvalId) {
+ try {
+ const res = await fetch(`/api/admin/approvals/${approvalId}`, {
+ headers: { 'Accept': 'application/json' },
+ });
+ const json = await res.json();
+
+ if (!json.success || !json.data?.content) {
+ showToast('데이터를 불러올 수 없습니다.', 'error');
+ return;
+ }
+
+ const content = json.data.content;
+ const expenseEl = document.getElementById('expense-form-container');
+ if (!expenseEl || !expenseEl._x_dataStack) return;
+
+ const alpine = expenseEl._x_dataStack[0];
+ const today = new Date().toISOString().slice(0, 10);
+
+ // 폼 데이터 복사 (날짜는 오늘로 초기화)
+ alpine.formData.expense_type = content.expense_type || 'corporate_card';
+ alpine.formData.tax_invoice = content.tax_invoice || 'normal';
+ alpine.formData.write_date = today;
+ alpine.formData.approval_date = today;
+ alpine.formData.department = content.department || '경리부';
+ alpine.formData.writer_name = content.writer_name || '';
+ alpine.formData.attachment_memo = content.attachment_memo || '';
+ alpine.formData.selected_card = content.selected_card || null;
+ alpine.formData.selected_account = content.selected_account || null;
+
+ // 내역 항목 복사 (날짜는 오늘로)
+ if (content.items && content.items.length > 0) {
+ let keyCounter = alpine.formData.items.length + 100;
+ alpine.formData.items = content.items.map(item => ({
+ _key: ++keyCounter,
+ date: today,
+ description: item.description || '',
+ amount: parseInt(item.amount) || 0,
+ vendor: item.vendor || '',
+ bank: item.bank || '',
+ account_no: item.account_no || '',
+ depositor: item.depositor || '',
+ remark: item.remark || '',
+ }));
+ }
+
+ // 제목 설정
+ const titleEl = document.getElementById('title');
+ if (!titleEl.value.trim()) {
+ titleEl.value = json.data.title || '지출결의서';
+ }
+
+ closeExpenseLoadModal();
+ showToast('지출결의서를 불러왔습니다. 내용을 확인 후 수정해주세요.', 'success');
+ } catch (e) {
+ showToast('불러오기 실패', 'error');
+ }
+}
+
+
@endpush
diff --git a/routes/api.php b/routes/api.php
index 38476164..e1457946 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -958,6 +958,7 @@
Route::put('/lines/{id}', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'updateLine'])->name('lines.update');
Route::delete('/lines/{id}', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'destroyLine'])->name('lines.destroy');
Route::get('/forms', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'forms'])->name('forms');
+ Route::get('/expense-history', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'expenseHistory'])->name('expense-history');
Route::get('/badge-counts', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'badgeCounts'])->name('badge-counts');
Route::post('/upload-file', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'uploadFile'])->name('upload-file');
Route::delete('/files/{fileId}', [\App\Http\Controllers\Api\Admin\ApprovalApiController::class, 'deleteFile'])->name('delete-file');