feat:입출금내역 계정과목 추가 및 엑셀 다운로드 기능

- BankTransaction 모델: 입출금 내역 저장 (계정과목 포함)
- 바로빌 데이터와 DB 저장 데이터 매칭하여 계정과목 유지
- 계정과목 드롭다운 선택 및 저장 기능
- 엑셀(CSV) 다운로드 기능
- 저장된 행은 녹색 배경으로 표시

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-23 11:09:36 +09:00
parent d212956f25
commit 3c32c5917c
4 changed files with 531 additions and 17 deletions

View File

@@ -5,12 +5,15 @@
use App\Http\Controllers\Controller;
use App\Models\Barobill\BarobillConfig;
use App\Models\Barobill\BarobillMember;
use App\Models\Barobill\BankTransaction;
use App\Models\Tenants\Tenant;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* 바로빌 계좌 입출금내역 조회 컨트롤러
@@ -203,9 +206,12 @@ public function transactions(Request $request): JsonResponse
$barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();
$userId = $barobillMember?->barobill_id ?? '';
// DB에서 저장된 계정과목 데이터 조회
$savedData = BankTransaction::getByDateRange($tenantId, $startDate, $endDate, $bankAccountNum ?: null);
// 전체 계좌 조회: 빈 값이면 모든 계좌의 거래 내역 조회
if (empty($bankAccountNum)) {
return $this->getAllAccountsTransactions($userId, $startDate, $endDate, $page, $limit);
return $this->getAllAccountsTransactions($userId, $startDate, $endDate, $page, $limit, $savedData);
}
// 단일 계좌 조회
@@ -252,8 +258,8 @@ public function transactions(Request $request): JsonResponse
]);
}
// 데이터 파싱
$logs = $this->parseTransactionLogs($resultData);
// 데이터 파싱 (저장된 계정과목 병합)
$logs = $this->parseTransactionLogs($resultData, '', $savedData);
return response()->json([
'success' => true,
@@ -280,7 +286,7 @@ public function transactions(Request $request): JsonResponse
/**
* 전체 계좌의 거래 내역 조회
*/
private function getAllAccountsTransactions(string $userId, string $startDate, string $endDate, int $page, int $limit): JsonResponse
private function getAllAccountsTransactions(string $userId, string $startDate, string $endDate, int $page, int $limit, $savedData = null): JsonResponse
{
// 먼저 계좌 목록 조회
$accountResult = $this->callSoap('GetBankAccountEx', ['AvailOnly' => 0]);
@@ -326,7 +332,7 @@ private function getAllAccountsTransactions(string $userId, string $startDate, s
$errorCode = $this->checkErrorCode($accData);
if (!$errorCode || in_array($errorCode, [-25005, -25001])) {
$parsed = $this->parseTransactionLogs($accData, $acc->BankName ?? '');
$parsed = $this->parseTransactionLogs($accData, $acc->BankName ?? '', $savedData);
foreach ($parsed['logs'] as $log) {
$log['bankName'] = $acc->BankName ?? $this->getBankName($acc->BankCode ?? '');
$allLogs[] = $log;
@@ -370,9 +376,9 @@ private function getAllAccountsTransactions(string $userId, string $startDate, s
}
/**
* 거래 내역 파싱
* 거래 내역 파싱 (저장된 계정과목 병합)
*/
private function parseTransactionLogs($resultData, string $defaultBankName = ''): array
private function parseTransactionLogs($resultData, string $defaultBankName = '', $savedData = null): array
{
$logs = [];
$totalDeposit = 0;
@@ -388,6 +394,7 @@ private function parseTransactionLogs($resultData, string $defaultBankName = '')
foreach ($rawLogs as $log) {
$deposit = floatval($log->Deposit ?? 0);
$withdraw = floatval($log->Withdraw ?? 0);
$balance = floatval($log->Balance ?? 0);
$totalDeposit += $deposit;
$totalWithdraw += $withdraw;
@@ -413,23 +420,35 @@ private function parseTransactionLogs($resultData, string $defaultBankName = '')
$fullSummary = $fullSummary ? $fullSummary . ' ' . $remark2 : $remark2;
}
$logs[] = [
$bankAccountNum = $log->BankAccountNum ?? '';
// 고유 키 생성하여 저장된 데이터와 매칭
$uniqueKey = implode('|', [$bankAccountNum, $transDT, $deposit, $withdraw, $balance]);
$savedItem = $savedData?->get($uniqueKey);
$logItem = [
'transDate' => $transDate,
'transTime' => $transTime,
'transDateTime' => $dateTime,
'bankAccountNum' => $log->BankAccountNum ?? '',
'bankAccountNum' => $bankAccountNum,
'bankName' => $log->BankName ?? $defaultBankName,
'deposit' => $deposit,
'withdraw' => $withdraw,
'depositFormatted' => number_format($deposit),
'withdrawFormatted' => number_format($withdraw),
'balance' => floatval($log->Balance ?? 0),
'balanceFormatted' => number_format(floatval($log->Balance ?? 0)),
'balance' => $balance,
'balanceFormatted' => number_format($balance),
'summary' => $fullSummary,
'cast' => $log->Cast ?? '',
'memo' => $log->Memo ?? '',
'transOffice' => $log->TransOffice ?? ''
'transOffice' => $log->TransOffice ?? '',
// 저장된 계정과목 정보 병합
'accountCode' => $savedItem?->account_code ?? '',
'accountName' => $savedItem?->account_name ?? '',
'isSaved' => $savedItem !== null,
];
$logs[] = $logItem;
}
return [
@@ -507,6 +526,233 @@ private function getBankName(string $code): string
return $banks[$code] ?? $code;
}
/**
* 계정과목 목록 조회
*/
public function accountCodes(): JsonResponse
{
// 자주 사용되는 계정과목 목록
$codes = [
['code' => '101', 'name' => '현금'],
['code' => '103', 'name' => '보통예금'],
['code' => '108', 'name' => '외상매출금'],
['code' => '110', 'name' => '받을어음'],
['code' => '253', 'name' => '외상매입금'],
['code' => '255', 'name' => '지급어음'],
['code' => '401', 'name' => '매출'],
['code' => '501', 'name' => '매입'],
['code' => '511', 'name' => '급여'],
['code' => '521', 'name' => '복리후생비'],
['code' => '522', 'name' => '여비교통비'],
['code' => '523', 'name' => '접대비'],
['code' => '524', 'name' => '통신비'],
['code' => '525', 'name' => '수도광열비'],
['code' => '526', 'name' => '세금과공과'],
['code' => '527', 'name' => '임차료'],
['code' => '528', 'name' => '수선비'],
['code' => '529', 'name' => '보험료'],
['code' => '530', 'name' => '차량유지비'],
['code' => '531', 'name' => '운반비'],
['code' => '532', 'name' => '교육훈련비'],
['code' => '533', 'name' => '도서인쇄비'],
['code' => '534', 'name' => '사무용품비'],
['code' => '535', 'name' => '소모품비'],
['code' => '536', 'name' => '지급수수료'],
['code' => '537', 'name' => '광고선전비'],
['code' => '538', 'name' => '대손상각비'],
['code' => '539', 'name' => '감가상각비'],
['code' => '540', 'name' => '잡비'],
['code' => '901', 'name' => '이자수익'],
['code' => '902', 'name' => '이자비용'],
['code' => '910', 'name' => '잡이익'],
['code' => '920', 'name' => '잡손실'],
];
return response()->json([
'success' => true,
'data' => $codes
]);
}
/**
* 입출금 내역 저장 (계정과목 포함)
*/
public function save(Request $request): JsonResponse
{
try {
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
$transactions = $request->input('transactions', []);
if (empty($transactions)) {
return response()->json([
'success' => false,
'error' => '저장할 데이터가 없습니다.'
]);
}
$saved = 0;
$updated = 0;
DB::beginTransaction();
foreach ($transactions as $trans) {
// 거래일시 생성
$transDt = ($trans['transDate'] ?? '') . ($trans['transTime'] ?? '');
$data = [
'tenant_id' => $tenantId,
'bank_account_num' => $trans['bankAccountNum'] ?? '',
'bank_code' => $trans['bankCode'] ?? '',
'bank_name' => $trans['bankName'] ?? '',
'trans_date' => $trans['transDate'] ?? '',
'trans_time' => $trans['transTime'] ?? '',
'trans_dt' => $transDt,
'deposit' => floatval($trans['deposit'] ?? 0),
'withdraw' => floatval($trans['withdraw'] ?? 0),
'balance' => floatval($trans['balance'] ?? 0),
'summary' => $trans['summary'] ?? '',
'cast' => $trans['cast'] ?? '',
'memo' => $trans['memo'] ?? '',
'trans_office' => $trans['transOffice'] ?? '',
'account_code' => $trans['accountCode'] ?? null,
'account_name' => $trans['accountName'] ?? null,
];
// Upsert: 있으면 업데이트, 없으면 생성
$existing = BankTransaction::where('tenant_id', $tenantId)
->where('bank_account_num', $data['bank_account_num'])
->where('trans_dt', $transDt)
->where('deposit', $data['deposit'])
->where('withdraw', $data['withdraw'])
->where('balance', $data['balance'])
->first();
if ($existing) {
// 계정과목만 업데이트
$existing->update([
'account_code' => $data['account_code'],
'account_name' => $data['account_name'],
]);
$updated++;
} else {
BankTransaction::create($data);
$saved++;
}
}
DB::commit();
return response()->json([
'success' => true,
'message' => "저장 완료: 신규 {$saved}건, 수정 {$updated}",
'saved' => $saved,
'updated' => $updated
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('입출금 내역 저장 오류: ' . $e->getMessage());
return response()->json([
'success' => false,
'error' => '저장 오류: ' . $e->getMessage()
]);
}
}
/**
* 엑셀 다운로드
*/
public function exportExcel(Request $request): StreamedResponse|JsonResponse
{
try {
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
$startDate = $request->input('startDate', date('Ymd'));
$endDate = $request->input('endDate', date('Ymd'));
$accountNum = $request->input('accountNum', '');
// DB에서 저장된 데이터 조회
$query = BankTransaction::where('tenant_id', $tenantId)
->whereBetween('trans_date', [$startDate, $endDate])
->orderBy('trans_date', 'desc')
->orderBy('trans_time', 'desc');
if (!empty($accountNum)) {
$query->where('bank_account_num', $accountNum);
}
$transactions = $query->get();
// 데이터가 없으면 바로빌에서 조회 (저장 안된 경우)
if ($transactions->isEmpty()) {
return response()->json([
'success' => false,
'error' => '저장된 데이터가 없습니다. 먼저 데이터를 조회하고 저장해주세요.'
]);
}
$filename = "입출금내역_{$startDate}_{$endDate}.csv";
return response()->streamDownload(function () use ($transactions) {
$handle = fopen('php://output', 'w');
// UTF-8 BOM for Excel
fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
// 헤더
fputcsv($handle, [
'거래일시',
'은행명',
'계좌번호',
'적요',
'입금',
'출금',
'잔액',
'상대방',
'계정과목코드',
'계정과목명'
]);
// 데이터
foreach ($transactions as $trans) {
$dateTime = '';
if ($trans->trans_date) {
$dateTime = substr($trans->trans_date, 0, 4) . '-' .
substr($trans->trans_date, 4, 2) . '-' .
substr($trans->trans_date, 6, 2);
if ($trans->trans_time) {
$dateTime .= ' ' . substr($trans->trans_time, 0, 2) . ':' .
substr($trans->trans_time, 2, 2) . ':' .
substr($trans->trans_time, 4, 2);
}
}
fputcsv($handle, [
$dateTime,
$trans->bank_name,
$trans->bank_account_num,
$trans->summary,
$trans->deposit,
$trans->withdraw,
$trans->balance,
$trans->cast,
$trans->account_code,
$trans->account_name
]);
}
fclose($handle);
}, $filename, [
'Content-Type' => 'text/csv; charset=utf-8',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
} catch (\Throwable $e) {
Log::error('엑셀 다운로드 오류: ' . $e->getMessage());
return response()->json([
'success' => false,
'error' => '다운로드 오류: ' . $e->getMessage()
]);
}
}
/**
* SOAP 호출
*/

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Models\Barobill;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\Tenants\Tenant;
/**
* 계좌 입출금 내역 모델
*
* 바로빌에서 조회한 입출금 내역에 계정과목을 추가하여 저장
*/
class BankTransaction extends Model
{
protected $fillable = [
'tenant_id',
'bank_account_num',
'bank_code',
'bank_name',
'trans_date',
'trans_time',
'trans_dt',
'deposit',
'withdraw',
'balance',
'summary',
'cast',
'memo',
'trans_office',
'account_code',
'account_name',
];
protected $casts = [
'deposit' => 'decimal:2',
'withdraw' => 'decimal:2',
'balance' => 'decimal:2',
];
/**
* 테넌트 관계
*/
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
}
/**
* 거래 고유 키 생성 (매칭용)
*/
public function getUniqueKeyAttribute(): string
{
return implode('|', [
$this->bank_account_num,
$this->trans_dt,
$this->deposit,
$this->withdraw,
$this->balance,
]);
}
/**
* 바로빌 로그 데이터로부터 고유 키 생성 (정적 메서드)
*/
public static function generateUniqueKey(array $log): string
{
// trans_dt 생성: transDate + transTime
$transDt = ($log['transDate'] ?? '') . ($log['transTime'] ?? '');
return implode('|', [
$log['bankAccountNum'] ?? '',
$transDt,
$log['deposit'] ?? 0,
$log['withdraw'] ?? 0,
$log['balance'] ?? 0,
]);
}
/**
* 테넌트별 거래 내역 조회 (기간별)
*/
public static function getByDateRange(int $tenantId, string $startDate, string $endDate, ?string $accountNum = null)
{
$query = self::where('tenant_id', $tenantId)
->whereBetween('trans_date', [$startDate, $endDate])
->orderBy('trans_date', 'desc')
->orderBy('trans_time', 'desc');
if ($accountNum) {
$query->where('bank_account_num', $accountNum);
}
return $query->get()->keyBy(fn($item) => $item->unique_key);
}
}

View File

@@ -63,12 +63,15 @@
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect, useRef } = React;
const { useState, useEffect, useRef, useCallback } = React;
// API Routes
const API = {
accounts: '{{ route("barobill.eaccount.accounts") }}',
transactions: '{{ route("barobill.eaccount.transactions") }}',
accountCodes: '{{ route("barobill.eaccount.account-codes") }}',
save: '{{ route("barobill.eaccount.save") }}',
export: '{{ route("barobill.eaccount.export") }}',
};
const CSRF_TOKEN = '{{ csrf_token() }}';
@@ -86,6 +89,15 @@
};
};
// Toast 알림
const showToast = (message, type = 'info') => {
if (window.showToast) {
window.showToast(message, type);
} else {
alert(message);
}
};
// StatCard Component
const StatCard = ({ title, value, subtext, icon, color = 'blue' }) => {
const colorClasses = {
@@ -138,8 +150,41 @@ className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
</div>
);
// AccountCodeSelect Component
const AccountCodeSelect = ({ value, onChange, accountCodes }) => (
<select
value={value || ''}
onChange={(e) => {
const selected = accountCodes.find(c => c.code === e.target.value);
onChange(e.target.value, selected?.name || '');
}}
className="w-full px-2 py-1 text-xs border border-stone-200 rounded focus:ring-2 focus:ring-emerald-500 outline-none bg-white"
>
<option value="">선택</option>
{accountCodes.map(code => (
<option key={code.code} value={code.code}>{code.code} {code.name}</option>
))}
</select>
);
// TransactionTable Component
const TransactionTable = ({ logs, loading, dateFrom, dateTo, onDateFromChange, onDateToChange, onThisMonth, onLastMonth, totalCount }) => {
const TransactionTable = ({
logs,
loading,
dateFrom,
dateTo,
onDateFromChange,
onDateToChange,
onThisMonth,
onLastMonth,
totalCount,
accountCodes,
onAccountCodeChange,
onSave,
onExport,
saving,
hasChanges
}) => {
const formatCurrency = (val) => new Intl.NumberFormat('ko-KR').format(val || 0) + '원';
if (loading) {
@@ -195,6 +240,40 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
</span>
</div>
</div>
{/* 저장/엑셀 버튼 */}
<div className="flex items-center gap-2 mt-4">
<button
onClick={onSave}
disabled={saving || logs.length === 0}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
hasChanges
? 'bg-emerald-600 text-white hover:bg-emerald-700'
: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200'
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
{saving ? (
<svg className="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
</svg>
)}
{hasChanges ? '변경사항 저장' : '저장'}
</button>
<button
onClick={onExport}
disabled={logs.length === 0}
className="flex items-center gap-2 px-4 py-2 bg-blue-100 text-blue-700 rounded-lg text-sm font-medium hover:bg-blue-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
엑셀 다운로드
</button>
</div>
</div>
<div className="overflow-x-auto" style={ {maxHeight: '500px', overflowY: 'auto'} }>
<table className="w-full text-left text-sm text-stone-600">
@@ -207,18 +286,19 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
<th className="px-4 py-4 text-right bg-stone-50 text-red-600">출금</th>
<th className="px-4 py-4 text-right bg-stone-50">잔액</th>
<th className="px-4 py-4 bg-stone-50">상대방</th>
<th className="px-4 py-4 bg-stone-50 min-w-[150px]">계정과목</th>
</tr>
</thead>
<tbody className="divide-y divide-stone-100">
{logs.length === 0 ? (
<tr>
<td colSpan="7" className="px-6 py-8 text-center text-stone-400">
<td colSpan="8" className="px-6 py-8 text-center text-stone-400">
해당 기간에 조회된 입출금 내역이 없습니다.
</td>
</tr>
) : (
logs.map((log, index) => (
<tr key={index} className="hover:bg-stone-50 transition-colors">
<tr key={index} className={`hover:bg-stone-50 transition-colors ${log.isSaved ? 'bg-green-50/30' : ''}`}>
<td className="px-4 py-3 whitespace-nowrap">
<div className="font-medium text-stone-900">{log.transDateTime || '-'}</div>
</td>
@@ -244,6 +324,16 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
<td className="px-4 py-3 text-stone-500">
{log.cast || '-'}
</td>
<td className="px-4 py-3">
<AccountCodeSelect
value={log.accountCode}
onChange={(code, name) => onAccountCodeChange(index, code, name)}
accountCodes={accountCodes}
/>
{log.accountName && (
<div className="text-xs text-emerald-600 mt-1">{log.accountName}</div>
)}
</td>
</tr>
))
)}
@@ -257,12 +347,15 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
// Main App Component
const App = () => {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [accounts, setAccounts] = useState([]);
const [selectedAccount, setSelectedAccount] = useState('');
const [logs, setLogs] = useState([]);
const [summary, setSummary] = useState({});
const [pagination, setPagination] = useState({});
const [error, setError] = useState(null);
const [accountCodes, setAccountCodes] = useState([]);
const [hasChanges, setHasChanges] = useState(false);
// 날짜 필터 상태 (기본: 현재 월)
const currentMonth = getMonthDates(0);
@@ -272,6 +365,7 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
// 초기 로드
useEffect(() => {
loadAccounts();
loadAccountCodes();
}, []);
// 날짜 또는 계좌 변경 시 거래내역 로드
@@ -293,9 +387,22 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
}
};
const loadAccountCodes = async () => {
try {
const response = await fetch(API.accountCodes);
const data = await response.json();
if (data.success) {
setAccountCodes(data.data || []);
}
} catch (err) {
console.error('계정과목 목록 로드 오류:', err);
}
};
const loadTransactions = async (page = 1) => {
setLoading(true);
setError(null);
setHasChanges(false);
try {
const params = new URLSearchParams({
@@ -325,6 +432,62 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
}
};
// 계정과목 변경 핸들러
const handleAccountCodeChange = useCallback((index, code, name) => {
setLogs(prevLogs => {
const newLogs = [...prevLogs];
newLogs[index] = {
...newLogs[index],
accountCode: code,
accountName: name
};
return newLogs;
});
setHasChanges(true);
}, []);
// 저장 핸들러
const handleSave = async () => {
if (logs.length === 0) return;
setSaving(true);
try {
const response = await fetch(API.save, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': CSRF_TOKEN,
'Accept': 'application/json'
},
body: JSON.stringify({ transactions: logs })
});
const data = await response.json();
if (data.success) {
showToast(data.message, 'success');
setHasChanges(false);
// 저장 후 다시 로드하여 isSaved 상태 갱신
loadTransactions();
} else {
showToast(data.error || '저장 실패', 'error');
}
} catch (err) {
showToast('저장 오류: ' + err.message, 'error');
} finally {
setSaving(false);
}
};
// 엑셀 다운로드 핸들러
const handleExport = () => {
const params = new URLSearchParams({
startDate: dateFrom.replace(/-/g, ''),
endDate: dateTo.replace(/-/g, ''),
accountNum: selectedAccount
});
window.location.href = `${API.export}?${params}`;
};
// 이번 달 버튼
const handleThisMonth = () => {
const dates = getMonthDates(0);
@@ -347,7 +510,7 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-stone-900">계좌 입출금내역</h1>
<p className="text-stone-500 mt-1">바로빌 API를 통한 계좌 입출금내역 조회</p>
<p className="text-stone-500 mt-1">바로빌 API를 통한 계좌 입출금내역 조회 계정과목 관리</p>
</div>
<div className="flex items-center gap-2">
@if($isTestMode)
@@ -441,6 +604,12 @@ className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-s
onThisMonth={handleThisMonth}
onLastMonth={handleLastMonth}
totalCount={summary.count || logs.length}
accountCodes={accountCodes}
onAccountCodeChange={handleAccountCodeChange}
onSave={handleSave}
onExport={handleExport}
saving={saving}
hasChanges={hasChanges}
/>
)}

View File

@@ -292,6 +292,9 @@
Route::get('/', [\App\Http\Controllers\Barobill\EaccountController::class, 'index'])->name('index');
Route::get('/accounts', [\App\Http\Controllers\Barobill\EaccountController::class, 'accounts'])->name('accounts');
Route::get('/transactions', [\App\Http\Controllers\Barobill\EaccountController::class, 'transactions'])->name('transactions');
Route::get('/account-codes', [\App\Http\Controllers\Barobill\EaccountController::class, 'accountCodes'])->name('account-codes');
Route::post('/save', [\App\Http\Controllers\Barobill\EaccountController::class, 'save'])->name('save');
Route::get('/export', [\App\Http\Controllers\Barobill\EaccountController::class, 'exportExcel'])->name('export');
});
});