From 2c98648fadeca738ca7f8ce82f0d88e40a6c39fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Fri, 20 Mar 2026 08:34:46 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[finance]=20=EA=B3=84=EC=A0=95=EB=B3=84?= =?UTF-8?q?=EC=9B=90=EC=9E=A5=20API=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=B3=B4=EA=B0=95=20(=EC=B9=B4=EB=93=9C=EA=B1=B0=EB=9E=98=20?= =?UTF-8?q?=EC=83=81=EC=84=B8,=20=EB=B6=84=EB=A6=AC=EC=A0=84=ED=91=9C=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A7=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/AccountLedgerService.php | 154 +++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 3 deletions(-) diff --git a/app/Services/AccountLedgerService.php b/app/Services/AccountLedgerService.php index f1ebd5cd..496106c4 100644 --- a/app/Services/AccountLedgerService.php +++ b/app/Services/AccountLedgerService.php @@ -2,6 +2,7 @@ namespace App\Services; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; class AccountLedgerService extends Service @@ -49,12 +50,19 @@ public function index(array $params): array 'tp.biz_no', 'jel.debit_amount', 'jel.credit_amount', - DB::raw("'journal' as source_type"), + DB::raw("COALESCE(je.source_type, 'journal') as source_type"), 'jel.journal_entry_id as source_id', + 'je.source_key', ]) ->orderBy('je.entry_date') ->get(); + // 분리 전표(split) 유효성 필터링 + $allLines = $this->filterSplitLines($tenantId, $allLines); + + // 카드거래 상세 조회 + $cardTxMap = $this->fetchCardTransactions($tenantId, $allLines); + $carryForward = $this->calculateCarryForward($tenantId, $accountCode, $startDate, $account->category); $isDebitNormal = in_array($account->category, ['asset', 'expense']); @@ -79,16 +87,22 @@ public function index(array $params): array $runningBalance += $isDebitNormal ? ($debit - $credit) : ($credit - $debit); + $cardTx = null; + if ($line->source_type === 'ecard_transaction' && $line->source_key) { + $cardTx = $cardTxMap[$line->source_key] ?? null; + } + $monthlyData[$month]['items'][] = [ 'date' => $line->date, 'description' => $line->description, - 'trading_partner_name' => $line->trading_partner_name, - 'biz_no' => $line->biz_no, + 'trading_partner_name' => $cardTx ? ($cardTx['merchant_name'] ?: $line->trading_partner_name) : $line->trading_partner_name, + 'biz_no' => $cardTx ? ($cardTx['merchant_biz_num'] ?: $line->biz_no) : $line->biz_no, 'debit_amount' => $debit, 'credit_amount' => $credit, 'balance' => $runningBalance, 'source_type' => $line->source_type, 'source_id' => (int) $line->source_id, + 'card_tx' => $cardTx, ]; $monthlyData[$month]['subtotal']['debit'] += $debit; @@ -115,6 +129,140 @@ public function index(array $params): array ]; } + /** + * 분리 전표(split) 유효성 필터링 + * — 삭제된 split은 제거, split이 존재하는 원본 전표도 제거 + */ + private function filterSplitLines(int $tenantId, Collection $lines): Collection + { + $ecardSplitLines = $lines->filter( + fn ($l) => $l->source_type === 'ecard_transaction' && $l->source_key && str_contains($l->source_key, '|split:') + ); + + if ($ecardSplitLines->isEmpty()) { + return $lines; + } + + $splitBaseKeys = $ecardSplitLines + ->map(fn ($l) => explode('|split:', $l->source_key)[0]) + ->unique() + ->all(); + + $validSplitIds = DB::table('barobill_card_transaction_splits') + ->where('tenant_id', $tenantId) + ->whereIn('original_unique_key', $splitBaseKeys) + ->pluck('id') + ->all(); + + $validSplitSuffixes = array_map(fn ($id) => '|split:'.$id, $validSplitIds); + + return $lines->filter(function ($l) use ($splitBaseKeys, $validSplitSuffixes) { + if ($l->source_type !== 'ecard_transaction' || ! $l->source_key) { + return true; + } + + if (str_contains($l->source_key, '|split:')) { + foreach ($validSplitSuffixes as $suffix) { + if (str_ends_with($l->source_key, $suffix)) { + return true; + } + } + + return false; + } + + // 원본(non-split) 전표: 분리 전표가 존재하면 제외 + return ! in_array($l->source_key, $splitBaseKeys); + })->values(); + } + + /** + * 카드거래 상세 일괄 조회 (source_key 기반) + */ + private function fetchCardTransactions(int $tenantId, Collection $lines): array + { + $sourceKeys = $lines + ->filter(fn ($l) => $l->source_type === 'ecard_transaction' && $l->source_key) + ->pluck('source_key') + ->unique() + ->values() + ->all(); + + if (empty($sourceKeys)) { + return []; + } + + // source_key = "card_num|use_dt|approval_num|approval_amount" 또는 "...|split:N" + $conditions = []; + $keyMap = []; + foreach ($sourceKeys as $key) { + $baseKey = explode('|split:', $key)[0]; + $parts = explode('|', $baseKey); + if (count($parts) === 4) { + $conditions[$baseKey] = $parts; + $keyMap[$key] = $baseKey; + } + } + $conditions = array_values($conditions); + + if (empty($conditions)) { + return []; + } + + $query = DB::table('barobill_card_transactions') + ->where('tenant_id', $tenantId); + + $query->where(function ($q) use ($conditions) { + foreach ($conditions as $c) { + $q->orWhere(function ($sub) use ($c) { + $sub->where('card_num', $c[0]) + ->where('use_dt', $c[1]) + ->where('approval_num', $c[2]) + ->whereRaw('CAST(approval_amount AS SIGNED) = ?', [(int) $c[3]]); + }); + } + }); + + $txs = $query->get(); + + $map = []; + foreach ($txs as $tx) { + $uniqueKey = implode('|', [ + $tx->card_num, + $tx->use_dt, + $tx->approval_num, + (int) $tx->approval_amount, + ]); + + $supplyAmount = $tx->modified_supply_amount !== null + ? (int) $tx->modified_supply_amount + : (int) $tx->approval_amount - (int) $tx->tax; + $taxAmount = $tx->modified_tax !== null + ? (int) $tx->modified_tax + : (int) $tx->tax; + + $map[$uniqueKey] = [ + 'card_num' => $tx->card_num, + 'card_company_name' => $tx->card_company_name, + 'merchant_name' => $tx->merchant_name, + 'merchant_biz_num' => $tx->merchant_biz_num, + 'deduction_type' => $tx->deduction_type, + 'supply_amount' => $supplyAmount, + 'tax_amount' => $taxAmount, + 'approval_amount' => (int) $tx->approval_amount, + ]; + } + + // 분리 키(uniqueKey|split:N)도 원본 카드 데이터로 매핑 + foreach ($keyMap as $sourceKey => $baseKey) { + if ($sourceKey !== $baseKey && isset($map[$baseKey])) { + $map[$sourceKey] = $map[$baseKey]; + } + } + + return $map; + } + private function calculateCarryForward(int $tenantId, string $accountCode, string $startDate, string $category): array { $sums = DB::table('journal_entry_lines as jel')