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 호출
*/