fix:엑셀 내보내기를 현재 화면 데이터 기반으로 변경

- GET → POST 방식으로 변경
- 저장된 DB 데이터 대신 현재 화면에 표시된 데이터 내보내기
- 프론트엔드에서 logs, splits 데이터를 JSON으로 전송
- Blob 다운로드 방식으로 파일 저장
- 금액 필드에 콤마 포맷팅 적용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-23 16:13:16 +09:00
parent 32a3987895
commit b934bc2e12
3 changed files with 101 additions and 74 deletions

View File

@@ -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'] ?? '',
''
]);
}

View File

@@ -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');
}
};
// 이번 달 버튼

View File

@@ -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');