- BankTransactionController: 은행 거래내역 조회 API - BankTransactionService: 은행 거래 조회 로직 - Swagger 문서화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
230 lines
9.0 KiB
PHP
230 lines
9.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Tenants\BankAccount;
|
|
use App\Models\Tenants\Deposit;
|
|
use App\Models\Tenants\Withdrawal;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* 은행 거래 (입출금 통합) 조회 서비스
|
|
*/
|
|
class BankTransactionService extends Service
|
|
{
|
|
/**
|
|
* 입출금 통합 목록 조회
|
|
*/
|
|
public function index(array $params): LengthAwarePaginator
|
|
{
|
|
$perPage = $params['per_page'] ?? 20;
|
|
$page = $params['page'] ?? 1;
|
|
$startDate = $params['start_date'] ?? null;
|
|
$endDate = $params['end_date'] ?? null;
|
|
$bankAccountId = $params['bank_account_id'] ?? null;
|
|
$transactionType = $params['transaction_type'] ?? null;
|
|
$search = $params['search'] ?? null;
|
|
$sortBy = $params['sort_by'] ?? 'transaction_date';
|
|
$sortDir = $params['sort_dir'] ?? 'desc';
|
|
|
|
$tenantId = $this->tenantId();
|
|
|
|
// 입금 쿼리 (payment_method = 'transfer')
|
|
$depositsQuery = DB::table('deposits')
|
|
->join('bank_accounts', 'deposits.bank_account_id', '=', 'bank_accounts.id')
|
|
->leftJoin('clients', 'deposits.client_id', '=', 'clients.id')
|
|
->where('deposits.tenant_id', $tenantId)
|
|
->where('deposits.payment_method', 'transfer')
|
|
->whereNull('deposits.deleted_at')
|
|
->select([
|
|
'deposits.id',
|
|
DB::raw("'deposit' as type"),
|
|
'deposits.deposit_date as transaction_date',
|
|
'bank_accounts.id as bank_account_id',
|
|
'bank_accounts.bank_name',
|
|
'bank_accounts.account_name',
|
|
'deposits.description as note',
|
|
'deposits.client_id as vendor_id',
|
|
DB::raw('COALESCE(clients.name, deposits.client_name) as vendor_name'),
|
|
'deposits.client_name as depositor_name',
|
|
'deposits.amount as deposit_amount',
|
|
DB::raw('0 as withdrawal_amount'),
|
|
'deposits.account_code as transaction_type',
|
|
DB::raw("CONCAT('deposit-', deposits.id) as source_id"),
|
|
'deposits.created_at',
|
|
'deposits.updated_at',
|
|
]);
|
|
|
|
// 출금 쿼리 (payment_method = 'transfer')
|
|
$withdrawalsQuery = DB::table('withdrawals')
|
|
->join('bank_accounts', 'withdrawals.bank_account_id', '=', 'bank_accounts.id')
|
|
->leftJoin('clients', 'withdrawals.client_id', '=', 'clients.id')
|
|
->where('withdrawals.tenant_id', $tenantId)
|
|
->where('withdrawals.payment_method', 'transfer')
|
|
->whereNull('withdrawals.deleted_at')
|
|
->select([
|
|
'withdrawals.id',
|
|
DB::raw("'withdrawal' as type"),
|
|
'withdrawals.withdrawal_date as transaction_date',
|
|
'bank_accounts.id as bank_account_id',
|
|
'bank_accounts.bank_name',
|
|
'bank_accounts.account_name',
|
|
'withdrawals.description as note',
|
|
'withdrawals.client_id as vendor_id',
|
|
DB::raw('COALESCE(clients.name, withdrawals.client_name) as vendor_name'),
|
|
'withdrawals.client_name as depositor_name',
|
|
DB::raw('0 as deposit_amount'),
|
|
'withdrawals.amount as withdrawal_amount',
|
|
'withdrawals.account_code as transaction_type',
|
|
DB::raw("CONCAT('withdrawal-', withdrawals.id) as source_id"),
|
|
'withdrawals.created_at',
|
|
'withdrawals.updated_at',
|
|
]);
|
|
|
|
// 필터 적용: 날짜 범위
|
|
if ($startDate) {
|
|
$depositsQuery->whereDate('deposits.deposit_date', '>=', $startDate);
|
|
$withdrawalsQuery->whereDate('withdrawals.withdrawal_date', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$depositsQuery->whereDate('deposits.deposit_date', '<=', $endDate);
|
|
$withdrawalsQuery->whereDate('withdrawals.withdrawal_date', '<=', $endDate);
|
|
}
|
|
|
|
// 필터 적용: 계좌
|
|
if ($bankAccountId) {
|
|
$depositsQuery->where('deposits.bank_account_id', $bankAccountId);
|
|
$withdrawalsQuery->where('withdrawals.bank_account_id', $bankAccountId);
|
|
}
|
|
|
|
// 필터 적용: 입출금유형 (account_code)
|
|
if ($transactionType) {
|
|
if ($transactionType === 'unset') {
|
|
$depositsQuery->whereNull('deposits.account_code');
|
|
$withdrawalsQuery->whereNull('withdrawals.account_code');
|
|
} else {
|
|
$depositsQuery->where('deposits.account_code', $transactionType);
|
|
$withdrawalsQuery->where('withdrawals.account_code', $transactionType);
|
|
}
|
|
}
|
|
|
|
// 필터 적용: 검색어
|
|
if ($search) {
|
|
$depositsQuery->where(function ($q) use ($search) {
|
|
$q->where('bank_accounts.bank_name', 'like', "%{$search}%")
|
|
->orWhere('bank_accounts.account_name', 'like', "%{$search}%")
|
|
->orWhere('deposits.client_name', 'like', "%{$search}%")
|
|
->orWhere('clients.name', 'like', "%{$search}%")
|
|
->orWhere('deposits.description', 'like', "%{$search}%");
|
|
});
|
|
|
|
$withdrawalsQuery->where(function ($q) use ($search) {
|
|
$q->where('bank_accounts.bank_name', 'like', "%{$search}%")
|
|
->orWhere('bank_accounts.account_name', 'like', "%{$search}%")
|
|
->orWhere('withdrawals.client_name', 'like', "%{$search}%")
|
|
->orWhere('clients.name', 'like', "%{$search}%")
|
|
->orWhere('withdrawals.description', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// UNION
|
|
$unionQuery = $depositsQuery->union($withdrawalsQuery);
|
|
|
|
// 정렬 컬럼 매핑
|
|
$sortColumn = match ($sortBy) {
|
|
'transaction_date' => 'transaction_date',
|
|
'deposit_amount', 'withdrawal_amount', 'amount' => DB::raw('(deposit_amount + withdrawal_amount)'),
|
|
default => 'transaction_date',
|
|
};
|
|
|
|
// 전체 조회하여 정렬 및 잔액 계산
|
|
$allItems = DB::query()
|
|
->fromSub($unionQuery, 'transactions')
|
|
->orderBy($sortColumn, $sortDir)
|
|
->get();
|
|
|
|
// 잔액 계산 (날짜순 정렬 기준)
|
|
$balance = 0;
|
|
$itemsWithBalance = $allItems->map(function ($item) use (&$balance) {
|
|
$balance += $item->deposit_amount - $item->withdrawal_amount;
|
|
$item->balance = $balance;
|
|
|
|
return $item;
|
|
});
|
|
|
|
// 수동 페이지네이션
|
|
$total = $itemsWithBalance->count();
|
|
$offset = ($page - 1) * $perPage;
|
|
$paginatedItems = $itemsWithBalance->slice($offset, $perPage)->values();
|
|
|
|
return new LengthAwarePaginator(
|
|
$paginatedItems,
|
|
$total,
|
|
$perPage,
|
|
$page,
|
|
['path' => request()->url()]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 입출금 요약 통계
|
|
*/
|
|
public function summary(array $params): array
|
|
{
|
|
$startDate = $params['start_date'] ?? null;
|
|
$endDate = $params['end_date'] ?? null;
|
|
$tenantId = $this->tenantId();
|
|
|
|
// 입금 합계 (계좌이체)
|
|
$depositQuery = Deposit::where('tenant_id', $tenantId)
|
|
->where('payment_method', 'transfer');
|
|
if ($startDate) {
|
|
$depositQuery->whereDate('deposit_date', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$depositQuery->whereDate('deposit_date', '<=', $endDate);
|
|
}
|
|
$totalDeposit = $depositQuery->sum('amount');
|
|
$depositUnsetCount = (clone $depositQuery)->whereNull('account_code')->count();
|
|
|
|
// 출금 합계 (계좌이체)
|
|
$withdrawalQuery = Withdrawal::where('tenant_id', $tenantId)
|
|
->where('payment_method', 'transfer');
|
|
if ($startDate) {
|
|
$withdrawalQuery->whereDate('withdrawal_date', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$withdrawalQuery->whereDate('withdrawal_date', '<=', $endDate);
|
|
}
|
|
$totalWithdrawal = $withdrawalQuery->sum('amount');
|
|
$withdrawalUnsetCount = (clone $withdrawalQuery)->whereNull('account_code')->count();
|
|
|
|
return [
|
|
'total_deposit' => (float) $totalDeposit,
|
|
'total_withdrawal' => (float) $totalWithdrawal,
|
|
'deposit_unset_count' => (int) $depositUnsetCount,
|
|
'withdrawal_unset_count' => (int) $withdrawalUnsetCount,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 계좌 목록 조회 (필터용)
|
|
*/
|
|
public function getAccountOptions(): array
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
return BankAccount::where('tenant_id', $tenantId)
|
|
->where('status', 'active')
|
|
->orderBy('bank_name')
|
|
->orderBy('account_name')
|
|
->get()
|
|
->map(fn ($acc) => [
|
|
'id' => $acc->id,
|
|
'label' => "{$acc->bank_name}|{$acc->account_name}",
|
|
])
|
|
->toArray();
|
|
}
|
|
}
|