From f068c969712248195cd35b19c4b01c800ee0dc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Thu, 19 Mar 2026 22:36:56 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[finance]=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=EA=B1=B0=EB=9E=98=EB=A5=BC=20=EA=B0=9C?= =?UTF-8?q?=EB=B3=84=20=ED=96=89=EC=9C=BC=EB=A1=9C=20=ED=99=95=EC=9E=A5=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 백엔드: 분리 항목을 CardTransactionSplit에서 조회하여 개별 행으로 확장 - 프론트: 분리#N 배지 추가, 각 분리 행이 자체 분개 표시 --- .../Finance/JournalEntryController.php | 122 ++++++++++-------- .../views/finance/journal-entries.blade.php | 37 ++---- 2 files changed, 78 insertions(+), 81 deletions(-) diff --git a/app/Http/Controllers/Finance/JournalEntryController.php b/app/Http/Controllers/Finance/JournalEntryController.php index ad1cac97..cc16c1e6 100644 --- a/app/Http/Controllers/Finance/JournalEntryController.php +++ b/app/Http/Controllers/Finance/JournalEntryController.php @@ -1008,11 +1008,63 @@ public function cardTransactions(Request $request): JsonResponse // 각 거래의 uniqueKey 수집 $uniqueKeys = array_column($logs, 'uniqueKey'); - // 분개 완료된 source_key 조회 (직접 매칭) - $journaledKeys = JournalEntry::getJournaledSourceKeys($tenantId, 'ecard_transaction', $uniqueKeys); + // 분리 데이터 조회 (uniqueKey별 splits) + $splitsGrouped = []; + if (! empty($uniqueKeys)) { + $splits = CardTransactionSplit::where('tenant_id', $tenantId) + ->whereIn('original_unique_key', $uniqueKeys) + ->orderBy('original_unique_key') + ->orderBy('sort_order') + ->get(); + foreach ($splits as $s) { + $splitsGrouped[$s->original_unique_key][] = $s; + } + } + + // 분리 거래 확장: 분리가 있는 카드거래는 분리 항목별 행으로 교체 + $expandedLogs = []; + foreach ($logs as $log) { + $key = $log['uniqueKey']; + $logSplits = $splitsGrouped[$key] ?? []; + + if (! empty($logSplits)) { + // 분리 거래: 각 split을 별도 행으로 확장 + foreach ($logSplits as $split) { + $splitSupply = (int) ($split->split_supply_amount ?? 0); + $splitTax = (int) ($split->split_tax ?? 0); + $splitAmount = $splitSupply + $splitTax; + + $expandedLogs[] = array_merge($log, [ + 'uniqueKey' => $key.'|split:'.$split->id, + 'parentUniqueKey' => $key, + 'isSplit' => true, + 'splitIndex' => $split->sort_order + 1, + 'splitTotal' => count($logSplits), + 'merchantName' => $split->description ?: $log['merchantName'], + 'deductionType' => $split->deduction_type ?? $log['deductionType'], + 'supplyAmount' => $splitSupply, + 'taxAmount' => $splitTax, + 'approvalAmount' => $splitAmount, + 'accountCode' => $split->account_code ?? $log['accountCode'], + 'accountName' => $split->account_name ?? $log['accountName'], + ]); + } + } else { + // 분리 없음: 원본 그대로 + $log['isSplit'] = false; + $expandedLogs[] = $log; + } + } + $logs = $expandedLogs; + + // 확장된 uniqueKey 수집 + $allSourceKeys = array_column($logs, 'uniqueKey'); + + // 분개 완료된 source_key 조회 (직접 + 분리 키 모두 포함) + $journaledKeys = JournalEntry::getJournaledSourceKeys($tenantId, 'ecard_transaction', $allSourceKeys); $journaledKeysMap = array_flip($journaledKeys); - // 분개된 전표 ID 조회 (직접 매칭) + // 분개된 전표 ID 조회 $journalMap = []; if (! empty($journaledKeys)) { $journals = JournalEntry::where('tenant_id', $tenantId) @@ -1025,59 +1077,13 @@ public function cardTransactions(Request $request): JsonResponse } } - // 분리 거래의 분개 상태 조회 (source_key LIKE 'uniqueKey|split:%') - $splitJournalMap = []; // originalKey => [['id' => ..., 'entry_no' => ...], ...] - if (! empty($uniqueKeys)) { - $splitJournals = JournalEntry::where('tenant_id', $tenantId) - ->where('source_type', 'ecard_transaction') - ->where(function ($q) use ($uniqueKeys) { - foreach ($uniqueKeys as $key) { - $q->orWhere('source_key', 'LIKE', $key.'|split:%'); - } - }) - ->select('id', 'source_key', 'entry_no') - ->get(); - - foreach ($splitJournals as $j) { - $originalKey = explode('|split:', $j->source_key)[0]; - $splitJournalMap[$originalKey][] = ['id' => $j->id, 'entry_no' => $j->entry_no]; - } - } - - // 분리 건수 조회 (전체 분개 완료 여부 판단용) - $splitCounts = []; - if (! empty($uniqueKeys)) { - $counts = CardTransactionSplit::where('tenant_id', $tenantId) - ->whereIn('original_unique_key', $uniqueKeys) - ->selectRaw('original_unique_key, COUNT(*) as cnt') - ->groupBy('original_unique_key') - ->pluck('cnt', 'original_unique_key'); - $splitCounts = $counts->toArray(); - } - // 각 거래에 분개 상태 추가 $journaledCount = 0; foreach ($logs as &$log) { $key = $log['uniqueKey'] ?? ''; - $directMatch = isset($journaledKeysMap[$key]); - $splitCount = $splitCounts[$key] ?? 0; - $splitJournals = $splitJournalMap[$key] ?? []; - $allSplitsJournaled = $splitCount > 0 && count($splitJournals) >= $splitCount; - - $log['hasJournal'] = $directMatch || $allSplitsJournaled; - $log['splitJournalIds'] = []; - - if ($directMatch) { - $log['journalId'] = $journalMap[$key]['id'] ?? null; - $log['journalEntryNo'] = $journalMap[$key]['entry_no'] ?? null; - } elseif (! empty($splitJournals)) { - $log['journalId'] = $splitJournals[0]['id']; - $log['journalEntryNo'] = $splitJournals[0]['entry_no']; - $log['splitJournalIds'] = array_column($splitJournals, 'id'); - } else { - $log['journalId'] = null; - $log['journalEntryNo'] = null; - } + $log['hasJournal'] = isset($journaledKeysMap[$key]); + $log['journalId'] = $journalMap[$key]['id'] ?? null; + $log['journalEntryNo'] = $journalMap[$key]['entry_no'] ?? null; if ($log['hasJournal']) { $journaledCount++; @@ -1085,12 +1091,18 @@ public function cardTransactions(Request $request): JsonResponse } unset($log); - // 통계 - $totalCount = count($logs); - $totalAmount = array_sum(array_column($logs, 'approvalAmount')); + // 통계 (원본 거래 기준) + $originalLogs = array_filter($logs, fn ($l) => ! ($l['isSplit'] ?? false)); + $splitParentKeys = array_unique(array_column( + array_filter($logs, fn ($l) => ($l['isSplit'] ?? false) && ($l['splitIndex'] ?? 0) === 1), + 'parentUniqueKey' + )); + $totalCount = count($originalLogs) + count($splitParentKeys); + $totalAmount = 0; $deductibleSum = 0; $nonDeductibleSum = 0; foreach ($logs as $log) { + $totalAmount += $log['approvalAmount']; if ($log['deductionType'] === 'non_deductible') { $nonDeductibleSum += $log['approvalAmount']; } else { diff --git a/resources/views/finance/journal-entries.blade.php b/resources/views/finance/journal-entries.blade.php index ee207bc4..98cd845a 100644 --- a/resources/views/finance/journal-entries.blade.php +++ b/resources/views/finance/journal-entries.blade.php @@ -1070,30 +1070,10 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f }); }); - // 2) 카드거래 행 + // 2) 카드거래 행 (분리 거래는 백엔드에서 이미 개별 행으로 확장됨) cardTransactionsList.forEach(ctx => { - let je = null; - let combinedLines = []; - let totalDebit = 0; - let totalCredit = 0; - - // 분리 분개: 복수 journal을 합산 - if (ctx.splitJournalIds && ctx.splitJournalIds.length > 0) { - ctx.splitJournalIds.forEach(id => { - linkedJournalIds.add(id); - const sje = journalMap[id]; - if (sje) { - combinedLines = combinedLines.concat(sje.lines || []); - totalDebit += sje.total_debit || 0; - totalCredit += sje.total_credit || 0; - } - }); - je = journalMap[ctx.splitJournalIds[0]]; - } else { - je = ctx.journalId ? journalMap[ctx.journalId] : null; - if (ctx.journalId) linkedJournalIds.add(ctx.journalId); - } - + const je = ctx.journalId ? journalMap[ctx.journalId] : null; + if (ctx.journalId) linkedJournalIds.add(ctx.journalId); rows.push({ type: 'card', key: `card-${ctx.uniqueKey}`, @@ -1106,9 +1086,9 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f hasJournal: ctx.hasJournal, journalId: ctx.journalId, entryNo: ctx.journalEntryNo || (je && je.entry_no) || null, - lines: combinedLines.length > 0 ? combinedLines : (je ? je.lines : []), - totalDebit: combinedLines.length > 0 ? totalDebit : (je ? je.total_debit : 0), - totalCredit: combinedLines.length > 0 ? totalCredit : (je ? je.total_credit : 0), + lines: je ? je.lines : [], + totalDebit: je ? je.total_debit : 0, + totalCredit: je ? je.total_credit : 0, bankTx: null, cardTx: ctx, sortKey: ctx.useDate + (ctx.useTime || '000000'), @@ -1408,6 +1388,11 @@ className={`px-2.5 py-1 text-xs rounded-full font-medium transition-colors ${vie
{row.type === 'card' && } + {row.type === 'card' && row.cardTx && row.cardTx.isSplit && ( + + 분리#{row.cardTx.splitIndex} + + )} {row.description} {row.type === 'card' && row.cardTx && (