diff --git a/app/Http/Controllers/Finance/ReceivableController.php b/app/Http/Controllers/Finance/ReceivableController.php index 99b5c363..4719d8e9 100644 --- a/app/Http/Controllers/Finance/ReceivableController.php +++ b/app/Http/Controllers/Finance/ReceivableController.php @@ -9,6 +9,7 @@ use App\Models\Finance\Receivable; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class ReceivableController extends Controller { @@ -194,6 +195,53 @@ public function ledger(Request $request): JsonResponse $partner = $request->input('partner'); $source = $request->input('source', 'all'); + // 분개 완료된 매출세금계산서 ID + $journaledInvoiceIds = []; + if ($source !== 'journal') { + $journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId) + ->where('invoice_type', 'sales') + ->distinct() + ->pluck('hometax_invoice_id') + ->toArray(); + } + + // 이월 잔액 계산 (startDate 이전) + $priorBalance = 0; + if ($startDate) { + if ($source !== 'journal') { + $priorHtQuery = HometaxInvoiceJournal::where('tenant_id', $tenantId) + ->where('account_code', '108') + ->where('invoice_type', 'sales') + ->where('write_date', '<', $startDate); + if ($partner) { + $priorHtQuery->where('trading_partner_name', 'like', "%{$partner}%"); + } + $priorHtData = $priorHtQuery->selectRaw('SUM(debit_amount) as td, SUM(credit_amount) as tc')->first(); + $priorBalance += ($priorHtData->td ?? 0) - ($priorHtData->tc ?? 0); + + $priorUnjQuery = HometaxInvoice::where('tenant_id', $tenantId) + ->where('invoice_type', 'sales') + ->whereNotIn('id', $journaledInvoiceIds) + ->where('write_date', '<', $startDate); + if ($partner) { + $priorUnjQuery->where('invoicee_corp_name', 'like', "%{$partner}%"); + } + $priorBalance += (int) $priorUnjQuery->sum('total_amount'); + } + if ($source !== 'hometax') { + $priorJnlQuery = JournalEntryLine::where('journal_entry_lines.tenant_id', $tenantId) + ->where('journal_entry_lines.account_code', '108') + ->join('journal_entries', 'journal_entries.id', '=', 'journal_entry_lines.journal_entry_id') + ->whereNull('journal_entries.deleted_at') + ->where('journal_entries.entry_date', '<', $startDate); + if ($partner) { + $priorJnlQuery->where('journal_entry_lines.trading_partner_name', 'like', "%{$partner}%"); + } + $priorJnlData = $priorJnlQuery->selectRaw('SUM(journal_entry_lines.debit_amount) as td, SUM(journal_entry_lines.credit_amount) as tc')->first(); + $priorBalance += ($priorJnlData->td ?? 0) - ($priorJnlData->tc ?? 0); + } + } + $items = collect(); // 1) 홈택스 매출세금계산서에서 외상매출금 조회 @@ -213,12 +261,6 @@ public function ledger(Request $request): JsonResponse $hometaxQuery->where('trading_partner_name', 'like', "%{$partner}%"); } - $journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId) - ->where('invoice_type', 'sales') - ->distinct() - ->pluck('hometax_invoice_id') - ->toArray(); - $hometaxItems = $hometaxQuery->get()->map(fn ($j) => [ 'date' => $j->write_date?->format('Y-m-d'), 'source' => 'hometax', @@ -293,9 +335,9 @@ public function ledger(Request $request): JsonResponse $items = $items->merge($journalItems); } - // 날짜순 정렬 + 누적잔액 계산 + // 날짜순 정렬 + 누적잔액 계산 (이월잔액부터 시작) $sorted = $items->sortBy('date')->values(); - $balance = 0; + $balance = $priorBalance; $sorted = $sorted->map(function ($item) use (&$balance) { $balance += $item['debitAmount'] - $item['creditAmount']; $item['balance'] = $balance; @@ -311,16 +353,17 @@ public function ledger(Request $request): JsonResponse 'success' => true, 'data' => $sorted->values(), 'stats' => [ + 'priorBalance' => $priorBalance, 'totalDebit' => $totalDebit, 'totalCredit' => $totalCredit, - 'balance' => $totalDebit - $totalCredit, + 'balance' => $priorBalance + $totalDebit - $totalCredit, 'partnerCount' => $partnerCount, ], ]); } /** - * 거래처별 외상매출금 요약 + * 거래처별 외상매출금 요약 (이월잔액 포함) */ public function summary(Request $request): JsonResponse { @@ -328,6 +371,79 @@ public function summary(Request $request): JsonResponse $startDate = $request->input('startDate'); $endDate = $request->input('endDate'); + // 분개 완료된 매출세금계산서 ID (전 기간) + $journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId) + ->where('invoice_type', 'sales') + ->distinct() + ->pluck('hometax_invoice_id') + ->toArray(); + + // 0. 이월 잔액 계산 (startDate 이전의 누적 잔액) + $priorBalanceMap = []; + + if ($startDate) { + // 이월 - 홈택스 분개 완료건 (account 108, sales) + HometaxInvoiceJournal::where('tenant_id', $tenantId) + ->where('account_code', '108') + ->where('invoice_type', 'sales') + ->where('write_date', '<', $startDate) + ->select( + 'trading_partner_name', + DB::raw('SUM(debit_amount) as total_debit'), + DB::raw('SUM(credit_amount) as total_credit') + ) + ->groupBy('trading_partner_name') + ->get() + ->each(function ($row) use (&$priorBalanceMap) { + $key = $row->trading_partner_name; + if (! isset($priorBalanceMap[$key])) { + $priorBalanceMap[$key] = ['debit' => 0, 'credit' => 0]; + } + $priorBalanceMap[$key]['debit'] += $row->total_debit; + $priorBalanceMap[$key]['credit'] += $row->total_credit; + }); + + // 이월 - 미분개 매출세금계산서 (차변 발생) + HometaxInvoice::where('tenant_id', $tenantId) + ->where('invoice_type', 'sales') + ->whereNotIn('id', $journaledInvoiceIds) + ->where('write_date', '<', $startDate) + ->select('invoicee_corp_name', DB::raw('SUM(total_amount) as total_amount')) + ->groupBy('invoicee_corp_name') + ->get() + ->each(function ($row) use (&$priorBalanceMap) { + $key = $row->invoicee_corp_name; + if (! isset($priorBalanceMap[$key])) { + $priorBalanceMap[$key] = ['debit' => 0, 'credit' => 0]; + } + $priorBalanceMap[$key]['debit'] += $row->total_amount; + }); + + // 이월 - 일반전표 (account 108) + JournalEntryLine::where('journal_entry_lines.tenant_id', $tenantId) + ->where('journal_entry_lines.account_code', '108') + ->join('journal_entries', function ($join) { + $join->on('journal_entries.id', '=', 'journal_entry_lines.journal_entry_id') + ->whereNull('journal_entries.deleted_at'); + }) + ->where('journal_entries.entry_date', '<', $startDate) + ->select( + 'journal_entry_lines.trading_partner_name', + DB::raw('SUM(journal_entry_lines.debit_amount) as total_debit'), + DB::raw('SUM(journal_entry_lines.credit_amount) as total_credit') + ) + ->groupBy('journal_entry_lines.trading_partner_name') + ->get() + ->each(function ($row) use (&$priorBalanceMap) { + $key = $row->trading_partner_name; + if (! isset($priorBalanceMap[$key])) { + $priorBalanceMap[$key] = ['debit' => 0, 'credit' => 0]; + } + $priorBalanceMap[$key]['debit'] += $row->total_debit; + $priorBalanceMap[$key]['credit'] += $row->total_credit; + }); + } + $items = collect(); // 1) 홈택스 매출세금계산서에서 외상매출금 @@ -343,12 +459,6 @@ public function summary(Request $request): JsonResponse $hometaxQuery->where('write_date', '<=', $endDate); } - $journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId) - ->where('invoice_type', 'sales') - ->distinct() - ->pluck('hometax_invoice_id') - ->toArray(); - $hometaxItems = $hometaxQuery->get()->map(fn ($j) => [ 'tradingPartnerName' => $j->trading_partner_name, 'debitAmount' => $j->dc_type === 'debit' ? (int) $j->debit_amount : 0, @@ -399,20 +509,42 @@ public function summary(Request $request): JsonResponse ]); $items = $items->merge($journalItems); - // 거래처별 그룹핑 - $grouped = $items->groupBy('tradingPartnerName')->map(function ($group, $partnerName) { + // 거래처별 그룹핑 (이월 잔액 포함) + $grouped = $items->groupBy('tradingPartnerName')->map(function ($group, $partnerName) use ($priorBalanceMap) { $totalDebit = $group->sum('debitAmount'); $totalCredit = $group->sum('creditAmount'); + $prior = $priorBalanceMap[$partnerName] ?? null; + $priorBalance = $prior ? ($prior['debit'] - $prior['credit']) : 0; return [ 'tradingPartnerName' => $partnerName ?: '(미지정)', + 'priorBalance' => $priorBalance, 'totalDebit' => $totalDebit, 'totalCredit' => $totalCredit, - 'balance' => $totalDebit - $totalCredit, + 'balance' => $priorBalance + $totalDebit - $totalCredit, 'lastTransactionDate' => $group->pluck('date')->filter()->sort()->last(), 'transactionCount' => $group->count(), ]; - })->sortByDesc('balance')->values(); + }); + + // 이월 잔액만 있고 당기 거래가 없는 거래처도 포함 + $existingPartners = $grouped->keys()->toArray(); + foreach ($priorBalanceMap as $partnerName => $prior) { + $priorBalance = $prior['debit'] - $prior['credit']; + if ($priorBalance != 0 && ! in_array($partnerName, $existingPartners)) { + $grouped[$partnerName] = [ + 'tradingPartnerName' => $partnerName ?: '(미지정)', + 'priorBalance' => $priorBalance, + 'totalDebit' => 0, + 'totalCredit' => 0, + 'balance' => $priorBalance, + 'lastTransactionDate' => null, + 'transactionCount' => 0, + ]; + } + } + + $grouped = $grouped->sortByDesc('balance')->values(); return response()->json([ 'success' => true, diff --git a/resources/views/finance/receivables.blade.php b/resources/views/finance/receivables.blade.php index 05ffb49e..b463cb00 100644 --- a/resources/views/finance/receivables.blade.php +++ b/resources/views/finance/receivables.blade.php @@ -67,7 +67,7 @@ function LoadingSpinner() { // ==================== 탭 1: 외상매출금 원장 ==================== function LedgerTab({ startDate, endDate, source, partnerSearch }) { const [items, setItems] = useState([]); - const [stats, setStats] = useState({ totalDebit: 0, totalCredit: 0, balance: 0, partnerCount: 0 }); + const [stats, setStats] = useState({ priorBalance: 0, totalDebit: 0, totalCredit: 0, balance: 0, partnerCount: 0 }); const [loading, setLoading] = useState(true); const fetchLedger = async () => { @@ -112,18 +112,25 @@ function LedgerTab({ startDate, endDate, source, partnerSearch }) { return (
{formatCurrency(stats.priorBalance)}원
+{formatCurrency(stats.totalDebit)}원
{formatCurrency(stats.totalCredit)}원
{formatCurrency(stats.balance)}원
+ {stats.priorBalance !== 0 &&이월 + 발생 - 회수
}{item.tradingPartnerName}