From b934bc2e12ffeca0149379874b6af17089ae5daa Mon Sep 17 00:00:00 2001 From: pro Date: Fri, 23 Jan 2026 16:13:16 +0900 Subject: [PATCH] =?UTF-8?q?fix:=EC=97=91=EC=85=80=20=EB=82=B4=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EA=B8=B0=EB=A5=BC=20=ED=98=84=EC=9E=AC=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET → POST 방식으로 변경 - 저장된 DB 데이터 대신 현재 화면에 표시된 데이터 내보내기 - 프론트엔드에서 logs, splits 데이터를 JSON으로 전송 - Blob 다운로드 방식으로 파일 저장 - 금액 필드에 콤마 포맷팅 적용 Co-Authored-By: Claude Opus 4.5 --- .../Controllers/Barobill/EcardController.php | 124 ++++++++---------- .../views/barobill/ecard/index.blade.php | 49 ++++++- routes/web.php | 2 +- 3 files changed, 101 insertions(+), 74 deletions(-) diff --git a/app/Http/Controllers/Barobill/EcardController.php b/app/Http/Controllers/Barobill/EcardController.php index 83f858e3..8ae02c8e 100644 --- a/app/Http/Controllers/Barobill/EcardController.php +++ b/app/Http/Controllers/Barobill/EcardController.php @@ -773,37 +773,22 @@ public function save(Request $request): JsonResponse public function exportExcel(Request $request): StreamedResponse|JsonResponse { try { - $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID); $startDate = $request->input('startDate', date('Ymd')); $endDate = $request->input('endDate', date('Ymd')); - $cardNum = $request->input('cardNum', ''); - - // DB에서 저장된 데이터 조회 - $query = CardTransaction::where('tenant_id', $tenantId) - ->whereBetween('use_date', [$startDate, $endDate]) - ->orderBy('use_date', 'desc') - ->orderBy('use_time', 'desc'); - - if (!empty($cardNum)) { - $query->where('card_num', $cardNum); - } - - $transactions = $query->get(); + $logs = $request->input('logs', []); + $splitsData = $request->input('splits', []); // 데이터가 없으면 안내 - if ($transactions->isEmpty()) { + if (empty($logs)) { return response()->json([ 'success' => false, - 'error' => '저장된 데이터가 없습니다. 먼저 데이터를 조회하고 저장해주세요.' + 'error' => '내보낼 데이터가 없습니다.' ]); } - // 분개 데이터 조회 - $splitsData = CardTransactionSplit::getByDateRange($tenantId, $startDate, $endDate); - $filename = "카드사용내역_{$startDate}_{$endDate}.csv"; - return response()->streamDownload(function () use ($transactions, $splitsData) { + return response()->streamDownload(function () use ($logs, $splitsData) { $handle = fopen('php://output', 'w'); // UTF-8 BOM for Excel @@ -827,44 +812,46 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse ]); // 데이터 - foreach ($transactions as $trans) { - $dateTime = ''; - if ($trans->use_date) { - $dateTime = substr($trans->use_date, 0, 4) . '-' . - substr($trans->use_date, 4, 2) . '-' . - substr($trans->use_date, 6, 2); - if ($trans->use_time) { - $dateTime .= ' ' . substr($trans->use_time, 0, 2) . ':' . - substr($trans->use_time, 2, 2) . ':' . - substr($trans->use_time, 4, 2); - } - } + foreach ($logs as $log) { + $dateTime = $log['useDateTime'] ?? ''; + $cardNum = $log['cardNum'] ?? ''; + $cardBrand = $log['cardBrand'] ?? ''; + $approvalNum = $log['approvalNum'] ?? ''; + $approvalAmount = $log['approvalAmount'] ?? 0; + $tax = $log['tax'] ?? 0; - // 해당 거래의 고유키 생성 - $uniqueKey = implode('|', [ - $trans->card_num, - $trans->use_dt, - $trans->approval_num, - (int) $trans->approval_amount, + // 고유키로 분개 데이터 확인 + $uniqueKey = $log['uniqueKey'] ?? implode('|', [ + $cardNum, + $log['useDt'] ?? '', + $approvalNum, + (int) $approvalAmount, ]); - // 분개 데이터 확인 $splits = $splitsData[$uniqueKey] ?? []; $hasSplits = count($splits) > 0; + // 공제여부 + $deductionType = $log['deductionType'] ?? ($log['merchantBizNum'] ? 'deductible' : 'non_deductible'); + $deductionText = ($deductionType === 'non_deductible') ? '불공' : '공제'; + + // 증빙/판매자상호, 내역 + $evidenceName = $log['evidenceName'] ?? $log['merchantName'] ?? ''; + $description = $log['description'] ?? $log['merchantBizType'] ?? ''; + if ($hasSplits) { // 분개가 있는 경우: 원본 행 (합계 표시) fputcsv($handle, [ '원본', $dateTime, - $trans->card_num, - $trans->card_company_name, + $cardNum, + $cardBrand, '-', // 분개된 경우 공제여부는 각 분개에서 표시 - $trans->evidence_name ?: $trans->merchant_name, - $trans->description ?: $trans->merchant_biz_type, - $trans->approval_amount, - $trans->tax, - $trans->approval_num, + $evidenceName, + $description, + number_format($approvalAmount), + number_format($tax), + $approvalNum, '-', // 분개된 경우 계정과목은 각 분개에서 표시 '분개됨 (' . count($splits) . '건)', '' @@ -872,41 +859,46 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse // 각 분개 행 출력 foreach ($splits as $index => $split) { - $deductionText = ($split->deduction_type === 'non_deductible') ? '불공' : '공제'; + $splitDeductionType = $split['deduction_type'] ?? $split['deductionType'] ?? 'deductible'; + $splitDeductionText = ($splitDeductionType === 'non_deductible') ? '불공' : '공제'; + $splitAmount = $split['split_amount'] ?? $split['amount'] ?? 0; + $splitEvidenceName = $split['evidence_name'] ?? $split['evidenceName'] ?? ''; + $splitDescription = $split['description'] ?? ''; + $splitAccountCode = $split['account_code'] ?? $split['accountCode'] ?? ''; + $splitAccountName = $split['account_name'] ?? $split['accountName'] ?? ''; + $splitMemo = $split['memo'] ?? ''; + fputcsv($handle, [ '└ 분개 #' . ($index + 1), '', // 사용일시 (원본과 동일하므로 생략) '', // 카드번호 '', // 카드사 - $deductionText, - $split->evidence_name ?: '', - $split->description ?: '', - $split->split_amount, + $splitDeductionText, + $splitEvidenceName, + $splitDescription, + number_format($splitAmount), '', // 부가세 (분개에서는 생략) '', // 승인번호 - $split->account_code ?: '', - $split->account_name ?: '', - $split->memo ?: '' + $splitAccountCode, + $splitAccountName, + $splitMemo ]); } } else { // 분개가 없는 경우: 일반 행 - $deductionType = $trans->deduction_type ?: ($trans->merchant_biz_num ? 'deductible' : 'non_deductible'); - $deductionText = ($deductionType === 'non_deductible') ? '불공' : '공제'; - fputcsv($handle, [ '일반', $dateTime, - $trans->card_num, - $trans->card_company_name, + $cardNum, + $cardBrand, $deductionText, - $trans->evidence_name ?: $trans->merchant_name, - $trans->description ?: $trans->merchant_biz_type, - $trans->approval_amount, - $trans->tax, - $trans->approval_num, - $trans->account_code ?: '', - $trans->account_name ?: '', + $evidenceName, + $description, + number_format($approvalAmount), + number_format($tax), + $approvalNum, + $log['accountCode'] ?? '', + $log['accountName'] ?? '', '' ]); } diff --git a/resources/views/barobill/ecard/index.blade.php b/resources/views/barobill/ecard/index.blade.php index f4563d8e..708b00d0 100644 --- a/resources/views/barobill/ecard/index.blade.php +++ b/resources/views/barobill/ecard/index.blade.php @@ -1128,13 +1128,48 @@ className="text-xs text-amber-600 hover:text-amber-700 underline" }; // 엑셀 다운로드 핸들러 - const handleExport = () => { - const params = new URLSearchParams({ - startDate: dateFrom.replace(/-/g, ''), - endDate: dateTo.replace(/-/g, ''), - cardNum: selectedCard - }); - window.location.href = `${API.export}?${params}`; + const handleExport = async () => { + if (logs.length === 0) { + notify('내보낼 데이터가 없습니다.', 'error'); + return; + } + + try { + const response = await fetch(API.export, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': CSRF_TOKEN, + 'Accept': 'text/csv' + }, + body: JSON.stringify({ + startDate: dateFrom.replace(/-/g, ''), + endDate: dateTo.replace(/-/g, ''), + logs: logs, + splits: splits + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + notify(errorData.error || '다운로드 실패', 'error'); + return; + } + + // Blob으로 파일 다운로드 + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `카드사용내역_${dateFrom.replace(/-/g, '')}_${dateTo.replace(/-/g, '')}.csv`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); + notify('엑셀 다운로드 완료', 'success'); + } catch (err) { + notify('다운로드 오류: ' + err.message, 'error'); + } }; // 이번 달 버튼 diff --git a/routes/web.php b/routes/web.php index 6ead76d1..c4f05375 100644 --- a/routes/web.php +++ b/routes/web.php @@ -308,7 +308,7 @@ Route::get('/transactions', [\App\Http\Controllers\Barobill\EcardController::class, 'transactions'])->name('transactions'); Route::get('/account-codes', [\App\Http\Controllers\Barobill\EcardController::class, 'accountCodes'])->name('account-codes'); Route::post('/save', [\App\Http\Controllers\Barobill\EcardController::class, 'save'])->name('save'); - Route::get('/export', [\App\Http\Controllers\Barobill\EcardController::class, 'exportExcel'])->name('export'); + Route::post('/export', [\App\Http\Controllers\Barobill\EcardController::class, 'exportExcel'])->name('export'); // 분개 관련 Route::get('/splits', [\App\Http\Controllers\Barobill\EcardController::class, 'splits'])->name('splits'); Route::post('/splits', [\App\Http\Controllers\Barobill\EcardController::class, 'saveSplits'])->name('splits.save');