From 1cd78585aedbc60a9fe30a0ca9f8f0ccf95772c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Tue, 24 Feb 2026 17:49:30 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[ecard]=20=EC=B9=B4=EB=93=9C=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=82=B4=EC=97=AD=20=EB=B6=84=EB=A6=AC/=EB=B6=84?= =?UTF-8?q?=EA=B0=9C=20=EC=97=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 '분개' 열을 '분리'(금액 나누기)와 '분개'(복식부기) 두 열로 분리 - SplitModal 텍스트를 '분개'에서 '분리'로 변경 - CSV 내보내기 텍스트도 '분리'로 통일 - 분리 열: 금액 분리 기능 (SplitModal) - 분개 열: 복식부기 분개 기능 (CardJournalModal) --- .../Controllers/Barobill/EcardController.php | 32 ++--- .../views/barobill/ecard/index.blade.php | 131 ++++++++++-------- 2 files changed, 89 insertions(+), 74 deletions(-) diff --git a/app/Http/Controllers/Barobill/EcardController.php b/app/Http/Controllers/Barobill/EcardController.php index 419239cb..b4915ca5 100644 --- a/app/Http/Controllers/Barobill/EcardController.php +++ b/app/Http/Controllers/Barobill/EcardController.php @@ -1093,7 +1093,7 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse // 카드번호를 문자형으로 강제 (엑셀 과학적 표기 방지) $cardNumText = $cardNum ? "=\"{$cardNum}\"" : ''; - // 고유키로 분개 데이터 확인 + // 고유키로 분리 데이터 확인 $uniqueKey = $log['uniqueKey'] ?? implode('|', [ $cardNum, $log['useDt'] ?? '', @@ -1117,7 +1117,7 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse $description = $log['description'] ?? $log['merchantBizType'] ?? ''; if ($hasSplits) { - // 분개가 있는 경우: 원본 행 (합계 표시) + // 분리가 있는 경우: 원본 행 (합계 표시) fputcsv($handle, [ '원본', $dateTime, @@ -1133,11 +1133,11 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse number_format($tax), $approvalNum, '-', - '분개됨 ('.count($splits).'건)', + '분리됨 ('.count($splits).'건)', '', ]); - // 각 분개 행 출력 + // 각 분리 행 출력 foreach ($splits as $index => $split) { $splitDeductionType = $split['deduction_type'] ?? $split['deductionType'] ?? 'deductible'; $splitDeductionText = ($splitDeductionType === 'non_deductible') ? '불공' : '공제'; @@ -1151,7 +1151,7 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse $splitMemo = $split['memo'] ?? ''; fputcsv($handle, [ - '└ 분개 #'.($index + 1), + '└ 분리 #'.($index + 1), '', '', '', @@ -1170,7 +1170,7 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse ]); } } else { - // 분개가 없는 경우: 일반 행 + // 분리가 없는 경우: 일반 행 fputcsv($handle, [ '일반', $dateTime, @@ -1208,7 +1208,7 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse } /** - * 분개 내역 조회 + * 분리 내역 조회 */ public function splits(Request $request): JsonResponse { @@ -1224,7 +1224,7 @@ public function splits(Request $request): JsonResponse 'data' => $splits, ]); } catch (\Throwable $e) { - Log::error('분개 내역 조회 오류: '.$e->getMessage()); + Log::error('분리 내역 조회 오류: '.$e->getMessage()); return response()->json([ 'success' => false, @@ -1234,7 +1234,7 @@ public function splits(Request $request): JsonResponse } /** - * 분개 저장 + * 분리 저장 */ public function saveSplits(Request $request): JsonResponse { @@ -1251,7 +1251,7 @@ public function saveSplits(Request $request): JsonResponse ]); } - // 분개 금액 합계 검증 (공급가액 + 부가세 합계) + // 분리 금액 합계 검증 (공급가액 + 부가세 합계) $originalAmount = floatval($originalData['originalAmount'] ?? 0); $splitTotal = array_sum(array_map(function ($s) { if (isset($s['supplyAmount']) && isset($s['tax'])) { @@ -1264,7 +1264,7 @@ public function saveSplits(Request $request): JsonResponse if (abs($originalAmount - $splitTotal) > 0.01) { return response()->json([ 'success' => false, - 'error' => "분개 금액 합계({$splitTotal})가 원본 금액({$originalAmount})과 일치하지 않습니다.", + 'error' => "분리 금액 합계({$splitTotal})가 원본 금액({$originalAmount})과 일치하지 않습니다.", ]); } @@ -1276,12 +1276,12 @@ public function saveSplits(Request $request): JsonResponse return response()->json([ 'success' => true, - 'message' => '분개가 저장되었습니다.', + 'message' => '분리가 저장되었습니다.', 'splitCount' => count($splits), ]); } catch (\Throwable $e) { DB::rollBack(); - Log::error('분개 저장 오류: '.$e->getMessage()); + Log::error('분리 저장 오류: '.$e->getMessage()); return response()->json([ 'success' => false, @@ -1291,7 +1291,7 @@ public function saveSplits(Request $request): JsonResponse } /** - * 분개 삭제 (원본으로 복원) + * 분리 삭제 (원본으로 복원) */ public function deleteSplits(Request $request): JsonResponse { @@ -1310,11 +1310,11 @@ public function deleteSplits(Request $request): JsonResponse return response()->json([ 'success' => true, - 'message' => '분개가 삭제되었습니다.', + 'message' => '분리가 삭제되었습니다.', 'deleted' => $deleted, ]); } catch (\Throwable $e) { - Log::error('분개 삭제 오류: '.$e->getMessage()); + Log::error('분리 삭제 오류: '.$e->getMessage()); return response()->json([ 'success' => false, diff --git a/resources/views/barobill/ecard/index.blade.php b/resources/views/barobill/ecard/index.blade.php index 2ee635e1..85ffeca1 100644 --- a/resources/views/barobill/ecard/index.blade.php +++ b/resources/views/barobill/ecard/index.blade.php @@ -256,7 +256,7 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${ ); }; - // SplitModal Component - 분개 모달 + // SplitModal Component - 분리 모달 const SplitModal = ({ isOpen, onClose, log, accountCodes, onSave, onReset, splits: existingSplits }) => { const [splits, setSplits] = useState([]); const [saving, setSaving] = useState(false); @@ -266,7 +266,7 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${ if (isOpen && log) { const defaultDeductionType = log.deductionType || (log.merchantBizNum ? 'deductible' : 'non_deductible'); if (existingSplits && existingSplits.length > 0) { - // 기존 분개 로드 + // 기존 분리 로드 setSplits(existingSplits.map(s => { const hasSupplyTax = s.split_supply_amount !== null && s.split_supply_amount !== undefined; return { @@ -281,7 +281,7 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${ }; })); } else { - // 새 분개: 원본의 공급가액/세액로 1개 행 생성 + // 새 분리: 원본의 공급가액/세액로 1개 행 생성 const origSupply = log.effectiveSupplyAmount ?? ((log.approvalAmount || 0) - (log.tax || 0)); const origTax = log.effectiveTax ?? (log.tax || 0); setSplits([{ @@ -333,7 +333,7 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${ const handleSave = async () => { if (!isValid) { - notify('분개 합계금액이 원본 금액과 일치하지 않습니다.', 'error'); + notify('분리 합계금액이 원본 금액과 일치하지 않습니다.', 'error'); return; } setSaving(true); @@ -343,7 +343,7 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${ }; const handleReset = async () => { - if (!confirm('분개를 삭제하고 원본 거래로 복구하시겠습니까?')) { + if (!confirm('분리를 삭제하고 원본 거래로 복구하시겠습니까?')) { return; } setResetting(true); @@ -371,7 +371,7 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${
-

거래 분개

+

거래 분리

@@ -510,13 +510,13 @@ className="mt-3 w-full py-2 border-2 border-dashed border-stone-300 text-stone-5 - 분개 항목 추가 + 분리 항목 추가
- 분개 합계 + 분리 합계 {formatCurrency(splitTotal)}원 {!isValid && ( @@ -533,7 +533,7 @@ className="mt-3 w-full py-2 border-2 border-dashed border-stone-300 text-stone-5 disabled={resetting} className="py-2 px-4 border border-red-300 text-red-600 rounded-lg hover:bg-red-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > - {resetting ? '복구 중...' : '분개 복구'} + {resetting ? '복구 중...' : '분리 복구'} )}
@@ -1447,6 +1447,7 @@ className="flex-1 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 t + @@ -1465,7 +1466,7 @@ className="flex-1 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 t {logs.length === 0 ? ( - @@ -1479,33 +1480,49 @@ className="flex-1 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 t {/* 원본 거래 행 */} + {/* 분리 열 */} + + {/* 분개 열 */} - {/* 분개 행들 */} + {/* 분리 행들 */} {hasSplits && logSplits.map((split, splitIdx) => { const splitSupply = split.split_supply_amount !== null && split.split_supply_amount !== undefined ? parseFloat(split.split_supply_amount) @@ -1741,8 +1755,9 @@ className="p-1 text-stone-400 hover:text-red-500 hover:bg-red-50 rounded transit +
분리 분개 사용일시 카드정보
+ 해당 기간에 조회된 카드 사용내역이 없습니다.
+ {(() => { + if (hasSplits) { + return ( + + ); + } else { + return ( + + ); + } + })()} + {(() => { const jInfo = journalMap[uniqueKey]; if (jInfo) { - // 복식부기 분개 완료 return ( - ); - } else if (hasSplits) { - // 구버전 splits만 존재 - return ( - ); } else { - // 분개 없음 return ( @@ -1683,12 +1700,9 @@ className={`w-full px-2 py-1 text-sm text-right border rounded focus:outline-non )} {hasSplits && ( - + + 분리됨 ({logSplits.length}건) + )} @@ -1727,7 +1741,7 @@ className="p-1 text-stone-400 hover:text-red-500 hover:bg-red-50 rounded transit
- └ 분개 #{splitIdx + 1} {split.memo && `- ${split.memo}`} + └ 분리 #{splitIdx + 1} {split.memo && `- ${split.memo}`} { try { const params = new URLSearchParams({ @@ -1996,7 +2011,7 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 // useEffect에서 splits 변경 감지하여 재계산 처리 } } catch (err) { - console.error('분개 데이터 로드 오류:', err); + console.error('분리 데이터 로드 오류:', err); } }; @@ -2082,7 +2097,7 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 } }; - // 요약 재계산: 분개가 있는 거래는 원본 대신 분개별 통계로 대체 + // 요약 재계산: 분리가 있는 거래는 원본 대신 분리별 통계로 대체 // 수정된 공급가액/세액이 있으면 해당 값으로 합계 반영 const recalculateSummary = (currentLogs, allSplits) => { if (!currentLogs || currentLogs.length === 0) return; @@ -2101,7 +2116,7 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 const logSplits = allSplits[uniqueKey] || []; if (logSplits.length > 0) { - // 분개가 있는 거래: 각 분개별로 계산 + // 분리가 있는 거래: 각 분리별로 계산 logSplits.forEach(split => { const splitSupply = split.split_supply_amount !== null && split.split_supply_amount !== undefined ? parseFloat(split.split_supply_amount) : parseFloat(split.split_amount || 0); @@ -2123,7 +2138,7 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 } }); } else { - // 분개가 없는 거래: 수정된 금액(effectiveSupplyAmount/effectiveTax)으로 계산 + // 분리가 없는 거래: 수정된 금액(effectiveSupplyAmount/effectiveTax)으로 계산 const type = log.deductionType || 'non_deductible'; const effSupply = Math.abs(log.effectiveSupplyAmount ?? ((log.approvalAmount || 0) - (log.tax || 0))); const effTax = Math.abs(log.effectiveTax ?? (log.tax || 0)); @@ -2162,14 +2177,14 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 if (logs.length > 0) { const totalCount = summary.count || 0; if (totalCount <= logs.length) { - // 전체 데이터가 한 페이지에 있으므로 분개 반영 재계산 + // 전체 데이터가 한 페이지에 있으므로 분리 반영 재계산 recalculateSummary(logs, splits); } // 페이지네이션 중이면 백엔드 전체 통계 유지 } }, [splits, logs]); - // 분개 모달 열기 + // 분리 모달 열기 const handleOpenSplitModal = (log, uniqueKey, existingSplits = []) => { setSplitModalLog(log); setSplitModalKey(uniqueKey); @@ -2177,7 +2192,7 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 setSplitModalOpen(true); }; - // 분개 저장 + // 분리 저장 const handleSaveSplits = async (log, splitData) => { try { const uniqueKey = splitModalKey; @@ -2205,18 +2220,18 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 const data = await response.json(); if (data.success) { notify(data.message, 'success'); - loadSplits(); // 분개 데이터 새로고침 + loadSplits(); // 분리 데이터 새로고침 } else { - notify(data.error || '분개 저장 실패', 'error'); + notify(data.error || '분리 저장 실패', 'error'); } } catch (err) { - notify('분개 저장 오류: ' + err.message, 'error'); + notify('분리 저장 오류: ' + err.message, 'error'); } }; - // 분개 삭제 + // 분리 삭제 const handleDeleteSplits = async (uniqueKey) => { - if (!confirm('분개를 삭제하시겠습니까? 원본 거래로 복원됩니다.')) return; + if (!confirm('분리를 삭제하시겠습니까? 원본 거래로 복원됩니다.')) return; try { const response = await fetch(API.deleteSplits, { @@ -2232,16 +2247,16 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 const data = await response.json(); if (data.success) { notify(data.message, 'success'); - loadSplits(); // 분개 데이터 새로고침 + loadSplits(); // 분리 데이터 새로고침 } else { - notify(data.error || '분개 삭제 실패', 'error'); + notify(data.error || '분리 삭제 실패', 'error'); } } catch (err) { - notify('분개 삭제 오류: ' + err.message, 'error'); + notify('분리 삭제 오류: ' + err.message, 'error'); } }; - // 분개 복구 (모달에서 호출) + // 분리 복구 (모달에서 호출) const handleResetSplits = async (log) => { const uniqueKey = log.uniqueKey || `${log.cardNum}|${log.useDt}|${log.approvalNum}|${Math.floor(log.approvalAmount)}`; try { @@ -2257,13 +2272,13 @@ className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-6 const data = await response.json(); if (data.success) { - notify('분개가 복구되었습니다.', 'success'); - loadSplits(); // 분개 데이터 새로고침 + notify('분리가 복구되었습니다.', 'success'); + loadSplits(); // 분리 데이터 새로고침 } else { - notify(data.error || '분개 복구 실패', 'error'); + notify(data.error || '분리 복구 실패', 'error'); } } catch (err) { - notify('분개 복구 오류: ' + err.message, 'error'); + notify('분리 복구 오류: ' + err.message, 'error'); } };