barobill_bank_transactions에서 계좌번호 매칭(하이픈 제거)으로 최신 거래일시를 서브쿼리로 조회하여 fallback 표시 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
358 lines
10 KiB
PHP
358 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Barobill\BankTransaction as BarobillBankTransaction;
|
|
use App\Models\Finance\BankAccount;
|
|
use App\Models\Finance\BankTransaction;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class BankAccountService
|
|
{
|
|
// =========================================================================
|
|
// 계좌 목록 조회
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 계좌 목록 조회 (페이지네이션)
|
|
*/
|
|
public function getAccounts(array $filters = [], int $perPage = 15): LengthAwarePaginator
|
|
{
|
|
$query = BankAccount::query();
|
|
|
|
// Soft Delete 필터
|
|
if (isset($filters['trashed'])) {
|
|
if ($filters['trashed'] === 'only') {
|
|
$query->onlyTrashed();
|
|
} elseif ($filters['trashed'] === 'with') {
|
|
$query->withTrashed();
|
|
}
|
|
}
|
|
|
|
// 검색 필터
|
|
if (! empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('bank_name', 'like', "%{$search}%")
|
|
->orWhere('account_number', 'like', "%{$search}%")
|
|
->orWhere('account_holder', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// 은행 필터
|
|
if (! empty($filters['bank_name'])) {
|
|
$query->where('bank_name', $filters['bank_name']);
|
|
}
|
|
|
|
// 예금종류 필터
|
|
if (! empty($filters['account_type'])) {
|
|
$query->where('account_type', $filters['account_type']);
|
|
}
|
|
|
|
// 활성 상태 필터 (status 기반)
|
|
if (isset($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
|
|
return $query
|
|
->addSelect(['*'])
|
|
->addSelect([
|
|
'latest_barobill_trans_date' => BarobillBankTransaction::select('trans_date')
|
|
->whereColumn('bank_account_num', DB::raw("REPLACE(bank_accounts.account_number, '-', '')"))
|
|
->orderByDesc('trans_date')
|
|
->orderByDesc('trans_time')
|
|
->limit(1),
|
|
'latest_barobill_trans_time' => BarobillBankTransaction::select('trans_time')
|
|
->whereColumn('bank_account_num', DB::raw("REPLACE(bank_accounts.account_number, '-', '')"))
|
|
->orderByDesc('trans_date')
|
|
->orderByDesc('trans_time')
|
|
->limit(1),
|
|
])
|
|
->orderBy('sort_order')
|
|
->orderBy('bank_name')
|
|
->paginate($perPage);
|
|
}
|
|
|
|
/**
|
|
* 모든 계좌 목록 (드롭다운용)
|
|
*/
|
|
public function getAllAccounts(): Collection
|
|
{
|
|
return BankAccount::active()
|
|
->ordered()
|
|
->get(['id', 'bank_name', 'account_number', 'account_type', 'balance']);
|
|
}
|
|
|
|
/**
|
|
* 은행별 통계
|
|
*/
|
|
public function getStatsByBank(): Collection
|
|
{
|
|
return BankAccount::active()
|
|
->select('bank_name')
|
|
->selectRaw('COUNT(*) as count')
|
|
->selectRaw('SUM(balance) as total_balance')
|
|
->groupBy('bank_name')
|
|
->orderBy('total_balance', 'desc')
|
|
->get();
|
|
}
|
|
|
|
// =========================================================================
|
|
// 계좌 CRUD
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 계좌 상세 조회
|
|
*/
|
|
public function getAccountById(int $id, bool $withTrashed = false): ?BankAccount
|
|
{
|
|
$query = BankAccount::query();
|
|
|
|
if ($withTrashed) {
|
|
$query->withTrashed();
|
|
}
|
|
|
|
return $query->find($id);
|
|
}
|
|
|
|
/**
|
|
* 계좌 생성
|
|
*/
|
|
public function createAccount(array $data): BankAccount
|
|
{
|
|
$data['created_by'] = auth()->id();
|
|
$data['tenant_id'] = $data['tenant_id'] ?? session('selected_tenant_id') ?? auth()->user()?->tenant_id;
|
|
|
|
return BankAccount::create($data);
|
|
}
|
|
|
|
/**
|
|
* 계좌 수정
|
|
*/
|
|
public function updateAccount(BankAccount $account, array $data): BankAccount
|
|
{
|
|
$data['updated_by'] = auth()->id();
|
|
|
|
$account->update($data);
|
|
|
|
return $account->fresh();
|
|
}
|
|
|
|
/**
|
|
* 계좌 삭제 (Soft Delete)
|
|
*/
|
|
public function deleteAccount(BankAccount $account): bool
|
|
{
|
|
$account->deleted_by = auth()->id();
|
|
$account->save();
|
|
|
|
return $account->delete();
|
|
}
|
|
|
|
/**
|
|
* 계좌 복원
|
|
*/
|
|
public function restoreAccount(BankAccount $account): bool
|
|
{
|
|
$account->deleted_by = null;
|
|
|
|
return $account->restore();
|
|
}
|
|
|
|
/**
|
|
* 계좌 영구 삭제
|
|
*/
|
|
public function forceDeleteAccount(BankAccount $account): bool
|
|
{
|
|
return $account->forceDelete();
|
|
}
|
|
|
|
// =========================================================================
|
|
// 계좌 상태 토글
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 활성/비활성 토글 (status 기반)
|
|
*/
|
|
public function toggleActive(BankAccount $account): BankAccount
|
|
{
|
|
$newStatus = $account->status === 'active' ? 'inactive' : 'active';
|
|
|
|
$account->update([
|
|
'status' => $newStatus,
|
|
'updated_by' => auth()->id(),
|
|
]);
|
|
|
|
return $account->fresh();
|
|
}
|
|
|
|
// =========================================================================
|
|
// 거래내역 관련
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 계좌의 거래내역 조회
|
|
*/
|
|
public function getTransactions(int $accountId, array $filters = [], int $perPage = 20): LengthAwarePaginator
|
|
{
|
|
$query = BankTransaction::forAccount($accountId);
|
|
|
|
// Soft Delete 필터
|
|
if (isset($filters['trashed'])) {
|
|
if ($filters['trashed'] === 'only') {
|
|
$query->onlyTrashed();
|
|
} elseif ($filters['trashed'] === 'with') {
|
|
$query->withTrashed();
|
|
}
|
|
}
|
|
|
|
// 검색 필터
|
|
if (! empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('description', 'like', "%{$search}%")
|
|
->orWhere('counterparty', 'like', "%{$search}%")
|
|
->orWhere('reference_number', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// 거래유형 필터
|
|
if (! empty($filters['transaction_type'])) {
|
|
$query->ofType($filters['transaction_type']);
|
|
}
|
|
|
|
// 날짜 범위 필터
|
|
if (! empty($filters['start_date']) && ! empty($filters['end_date'])) {
|
|
$query->dateBetween($filters['start_date'], $filters['end_date']);
|
|
} elseif (! empty($filters['start_date'])) {
|
|
$query->where('transaction_date', '>=', $filters['start_date']);
|
|
} elseif (! empty($filters['end_date'])) {
|
|
$query->where('transaction_date', '<=', $filters['end_date']);
|
|
}
|
|
|
|
// 대사 상태 필터
|
|
if (isset($filters['is_reconciled'])) {
|
|
$query->reconciled((bool) $filters['is_reconciled']);
|
|
}
|
|
|
|
return $query
|
|
->with('bankAccount:id,bank_name,account_number')
|
|
->latest()
|
|
->paginate($perPage);
|
|
}
|
|
|
|
/**
|
|
* 거래내역 생성
|
|
*/
|
|
public function createTransaction(array $data): BankTransaction
|
|
{
|
|
return DB::transaction(function () use ($data) {
|
|
$account = BankAccount::findOrFail($data['bank_account_id']);
|
|
|
|
// 거래 후 잔액 계산
|
|
$newBalance = match ($data['transaction_type']) {
|
|
BankTransaction::TYPE_DEPOSIT => $account->balance + $data['amount'],
|
|
BankTransaction::TYPE_WITHDRAWAL, BankTransaction::TYPE_TRANSFER => $account->balance - $data['amount'],
|
|
default => $account->balance,
|
|
};
|
|
|
|
$data['balance_after'] = $newBalance;
|
|
$data['tenant_id'] = $account->tenant_id;
|
|
$data['created_by'] = auth()->id();
|
|
|
|
$transaction = BankTransaction::create($data);
|
|
|
|
// 계좌 잔액 업데이트
|
|
$account->updateBalance($newBalance);
|
|
|
|
return $transaction;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 거래내역 수정
|
|
*/
|
|
public function updateTransaction(BankTransaction $transaction, array $data): BankTransaction
|
|
{
|
|
$data['updated_by'] = auth()->id();
|
|
|
|
$transaction->update($data);
|
|
|
|
return $transaction->fresh();
|
|
}
|
|
|
|
/**
|
|
* 거래내역 삭제
|
|
*/
|
|
public function deleteTransaction(BankTransaction $transaction): bool
|
|
{
|
|
$transaction->deleted_by = auth()->id();
|
|
$transaction->save();
|
|
|
|
return $transaction->delete();
|
|
}
|
|
|
|
// =========================================================================
|
|
// 일괄 작업
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 일괄 삭제
|
|
*/
|
|
public function bulkDelete(array $ids): int
|
|
{
|
|
return BankAccount::whereIn('id', $ids)
|
|
->update([
|
|
'deleted_by' => auth()->id(),
|
|
'deleted_at' => now(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 일괄 복원
|
|
*/
|
|
public function bulkRestore(array $ids): int
|
|
{
|
|
return BankAccount::onlyTrashed()
|
|
->whereIn('id', $ids)
|
|
->update([
|
|
'deleted_by' => null,
|
|
'deleted_at' => null,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 일괄 영구 삭제
|
|
*/
|
|
public function bulkForceDelete(array $ids): int
|
|
{
|
|
return BankAccount::onlyTrashed()
|
|
->whereIn('id', $ids)
|
|
->forceDelete();
|
|
}
|
|
|
|
// =========================================================================
|
|
// 요약 및 통계
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 전체 요약 통계
|
|
*/
|
|
public function getSummary(): array
|
|
{
|
|
$accounts = BankAccount::active()->get();
|
|
|
|
return [
|
|
'total_accounts' => $accounts->count(),
|
|
'total_balance' => $accounts->sum('balance'),
|
|
'formatted_total_balance' => number_format($accounts->sum('balance')) . '원',
|
|
'by_bank' => $accounts->groupBy('bank_name')->map(fn($group) => [
|
|
'count' => $group->count(),
|
|
'total' => $group->sum('balance'),
|
|
]),
|
|
];
|
|
}
|
|
}
|