diff --git a/resources/views/finance/journal-entries.blade.php b/resources/views/finance/journal-entries.blade.php index f9ecc6f2..e4e73ec2 100644 --- a/resources/views/finance/journal-entries.blade.php +++ b/resources/views/finance/journal-entries.blade.php @@ -893,156 +893,363 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f }; // ============================================================ -// JournalEntryList (전표 목록 - 수동/은행거래 통합) +// JournalEntryList (은행거래 + 수동전표 통합 목록) // ============================================================ -const JournalEntryList = ({ refreshKey, onEdit }) => { - const [entries, setEntries] = useState([]); +const JournalEntryList = ({ accountCodes, tradingPartners, onPartnerAdded, refreshKey, onEdit }) => { + const [transactions, setTransactions] = useState([]); + const [journalEntries, setJournalEntries] = useState([]); const [loading, setLoading] = useState(false); + const [accounts, setAccounts] = useState([]); + const [journalStats, setJournalStats] = useState({}); const [dateRange, setDateRange] = useState({ - start: getKoreanDate(-30), - end: getKoreanDate(), + start: getKoreanDate(-30).replace(/-/g, ''), + end: getKoreanDate().replace(/-/g, ''), }); - const [sourceFilter, setSourceFilter] = useState('all'); + const [selectedAccount, setSelectedAccount] = useState(''); + const [viewFilter, setViewFilter] = useState('all'); // all, bank, manual, unjournaled - const fetchEntries = async () => { + // 분개 모달 상태 (은행거래 → 전표 생성) + const [showJournalModal, setShowJournalModal] = useState(false); + const [modalTransaction, setModalTransaction] = useState(null); + + const setMonthRange = (offset) => { + const now = new Date(); + const target = new Date(now.getFullYear(), now.getMonth() + offset, 1); + const start = target.toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, ''); + const endDate = offset === 0 + ? getKoreanDate().replace(/-/g, '') + : new Date(target.getFullYear(), target.getMonth() + 1, 0).toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, ''); + setDateRange({ start, end: endDate }); + }; + + const fetchData = async () => { setLoading(true); try { - const params = new URLSearchParams({ start_date: dateRange.start, end_date: dateRange.end }); - const res = await fetch(`/finance/journal-entries/list?${params}`); - const data = await res.json(); - if (data.success) { - setEntries(data.data || []); + const bankParams = new URLSearchParams({ startDate: dateRange.start, endDate: dateRange.end }); + if (selectedAccount) bankParams.set('accountNum', selectedAccount); + const journalParams = new URLSearchParams({ + start_date: formatDate(dateRange.start), + end_date: formatDate(dateRange.end), + }); + + const [bankRes, journalRes] = await Promise.all([ + fetch(`/finance/journal-entries/bank-transactions?${bankParams}`), + fetch(`/finance/journal-entries/list?${journalParams}`), + ]); + const bankData = await bankRes.json(); + const journalData = await journalRes.json(); + + if (bankData.success) { + setTransactions(bankData.data?.logs || []); + setJournalStats(bankData.data?.journalStats || {}); + if (bankData.data?.accounts) setAccounts(bankData.data.accounts); + } + if (journalData.success) { + setJournalEntries(journalData.data || []); } } catch (err) { - console.error('전표 목록 조회 실패:', err); + console.error('데이터 조회 실패:', err); + notify('데이터 조회 중 오류가 발생했습니다.', 'error'); } finally { setLoading(false); } }; - useEffect(() => { fetchEntries(); }, [refreshKey]); + useEffect(() => { fetchData(); }, [refreshKey]); - const manualCount = entries.filter(e => !e.source_type || e.source_type === 'manual').length; - const bankCount = entries.filter(e => e.source_type === 'bank_transaction').length; + // 전표 맵 생성 (journalId → entry) + const journalMap = {}; + journalEntries.forEach(je => { journalMap[je.id] = je; }); - const filtered = entries.filter(e => { - if (sourceFilter === 'manual') return !e.source_type || e.source_type === 'manual'; - if (sourceFilter === 'bank') return e.source_type === 'bank_transaction'; + // 통합 행 생성 + const bankLinkedJournalIds = new Set(); + const rows = []; + + // 1) 은행거래 행 + transactions.forEach(tx => { + const je = tx.journalId ? journalMap[tx.journalId] : null; + if (tx.journalId) bankLinkedJournalIds.add(tx.journalId); + rows.push({ + type: 'bank', + key: `bank-${tx.uniqueKey}`, + date: tx.transDate, + time: tx.transTime, + description: tx.summary || '-', + deposit: parseFloat(tx.deposit) || 0, + withdraw: parseFloat(tx.withdraw) || 0, + balance: tx.balance, + hasJournal: tx.hasJournal, + journalId: tx.journalId, + entryNo: tx.journalEntryNo || (je && je.entry_no) || null, + lines: je ? je.lines : [], + totalDebit: je ? je.total_debit : 0, + totalCredit: je ? je.total_credit : 0, + bankTx: tx, + sortKey: tx.transDate + (tx.transTime || '000000'), + }); + }); + + // 2) 수동 전표 행 (은행거래에 연결되지 않은 것만) + journalEntries.forEach(je => { + if (bankLinkedJournalIds.has(je.id)) return; + if (je.source_type === 'bank_transaction') return; + rows.push({ + type: 'manual', + key: `manual-${je.id}`, + date: je.entry_date.replace(/-/g, ''), + time: '', + description: je.description || '-', + deposit: 0, + withdraw: 0, + balance: null, + hasJournal: true, + journalId: je.id, + entryNo: je.entry_no, + lines: je.lines || [], + totalDebit: je.total_debit, + totalCredit: je.total_credit, + bankTx: null, + sortKey: je.entry_date.replace(/-/g, '') + '999999', + }); + }); + + // 최신순 정렬 + rows.sort((a, b) => b.sortKey.localeCompare(a.sortKey)); + + // 필터 적용 + const filtered = rows.filter(r => { + if (viewFilter === 'bank') return r.type === 'bank'; + if (viewFilter === 'manual') return r.type === 'manual'; + if (viewFilter === 'unjournaled') return r.type === 'bank' && !r.hasJournal; return true; }); - const totalDebit = filtered.reduce((s, e) => s + (e.total_debit || 0), 0); - const totalCredit = filtered.reduce((s, e) => s + (e.total_credit || 0), 0); + // 통계 + const bankRows = rows.filter(r => r.type === 'bank'); + const manualRows = rows.filter(r => r.type === 'manual'); + const depositSum = bankRows.reduce((s, r) => s + r.deposit, 0); + const withdrawSum = bankRows.reduce((s, r) => s + r.withdraw, 0); + const totalDebit = filtered.reduce((s, r) => s + (r.totalDebit || 0), 0); + const totalCredit = filtered.reduce((s, r) => s + (r.totalCredit || 0), 0); - const setMonthRange = (offset) => { - const now = new Date(); - const target = new Date(now.getFullYear(), now.getMonth() + offset, 1); - const start = target.toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }); - const end = offset === 0 - ? getKoreanDate() - : new Date(target.getFullYear(), target.getMonth() + 1, 0).toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }); - setDateRange({ start, end }); + const handleJournal = (tx) => { + setModalTransaction(tx); + setShowJournalModal(true); + }; + + const handleJournalSaved = () => { + setShowJournalModal(false); + setModalTransaction(null); + fetchData(); + }; + + const handleJournalDeleted = () => { + setShowJournalModal(false); + setModalTransaction(null); + fetchData(); }; return ( -