Files
sam-api/app/Services/BankTransactionService.php
kent 2aa1d78e62 feat: I-4 거래통장 조회 API 구현
- BankTransactionController: 은행 거래내역 조회 API
- BankTransactionService: 은행 거래 조회 로직
- Swagger 문서화

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 15:47:03 +09:00

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();
}
}