Files
sam-manage/app/Models/Finance/BankTransaction.php
2026-01-20 20:21:06 +09:00

254 lines
5.9 KiB
PHP

<?php
namespace App\Models\Finance;
use App\Models\Tenants\Tenant;
use App\Traits\BelongsToTenant;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* 은행 거래내역 모델
*/
class BankTransaction extends Model
{
use BelongsToTenant, ModelTrait, SoftDeletes;
// 거래 유형 상수
public const TYPE_DEPOSIT = 'deposit'; // 입금
public const TYPE_WITHDRAWAL = 'withdrawal'; // 출금
public const TYPE_TRANSFER = 'transfer'; // 이체
protected $fillable = [
'tenant_id',
'bank_account_id',
'transaction_type',
'amount',
'balance_after',
'transaction_date',
'transaction_time',
'description',
'counterparty',
'reference_number',
'category',
'related_order_id',
'related_payment_id',
'is_reconciled',
'reconciled_at',
'memo',
'options',
'created_by',
'updated_by',
'deleted_by',
];
protected $hidden = [
'created_by',
'updated_by',
'deleted_by',
'deleted_at',
];
protected $casts = [
'amount' => 'decimal:2',
'balance_after' => 'decimal:2',
'transaction_date' => 'date',
'is_reconciled' => 'boolean',
'reconciled_at' => 'datetime',
'options' => 'array',
];
// ============================================================
// 관계 정의
// ============================================================
/**
* 테넌트
*/
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
}
/**
* 계좌
*/
public function bankAccount(): BelongsTo
{
return $this->belongsTo(BankAccount::class, 'bank_account_id');
}
// ============================================================
// Accessors
// ============================================================
/**
* 입금 여부
*/
public function getIsDepositAttribute(): bool
{
return $this->transaction_type === self::TYPE_DEPOSIT;
}
/**
* 출금 여부
*/
public function getIsWithdrawalAttribute(): bool
{
return $this->transaction_type === self::TYPE_WITHDRAWAL;
}
/**
* 포맷된 금액 (부호 포함)
*/
public function getFormattedAmountAttribute(): string
{
$prefix = $this->is_deposit ? '+' : '-';
return $prefix . number_format(abs($this->amount)) . '원';
}
/**
* 포맷된 잔액
*/
public function getFormattedBalanceAfterAttribute(): string
{
return number_format($this->balance_after) . '원';
}
/**
* 거래 유형 라벨
*/
public function getTypeLabel(): string
{
return match ($this->transaction_type) {
self::TYPE_DEPOSIT => '입금',
self::TYPE_WITHDRAWAL => '출금',
self::TYPE_TRANSFER => '이체',
default => '기타',
};
}
/**
* 거래 유형 색상 클래스
*/
public function getTypeColorClass(): string
{
return match ($this->transaction_type) {
self::TYPE_DEPOSIT => 'text-green-600',
self::TYPE_WITHDRAWAL => 'text-red-600',
self::TYPE_TRANSFER => 'text-blue-600',
default => 'text-gray-600',
};
}
// ============================================================
// Scopes
// ============================================================
/**
* 계좌별 필터
*/
public function scopeForAccount($query, int $accountId)
{
return $query->where('bank_account_id', $accountId);
}
/**
* 거래유형별 필터
*/
public function scopeOfType($query, string $type)
{
return $query->where('transaction_type', $type);
}
/**
* 입금만
*/
public function scopeDeposits($query)
{
return $query->where('transaction_type', self::TYPE_DEPOSIT);
}
/**
* 출금만
*/
public function scopeWithdrawals($query)
{
return $query->where('transaction_type', self::TYPE_WITHDRAWAL);
}
/**
* 날짜 범위
*/
public function scopeDateBetween($query, $startDate, $endDate)
{
return $query->whereBetween('transaction_date', [$startDate, $endDate]);
}
/**
* 대사 완료 여부
*/
public function scopeReconciled($query, bool $isReconciled = true)
{
return $query->where('is_reconciled', $isReconciled);
}
/**
* 최신순 정렬
*/
public function scopeLatest($query)
{
return $query->orderBy('transaction_date', 'desc')
->orderBy('transaction_time', 'desc')
->orderBy('id', 'desc');
}
// ============================================================
// 메서드
// ============================================================
/**
* 대사 완료 처리
*/
public function markAsReconciled(): void
{
$this->update([
'is_reconciled' => true,
'reconciled_at' => now(),
]);
}
/**
* 대사 취소
*/
public function unmarkReconciled(): void
{
$this->update([
'is_reconciled' => false,
'reconciled_at' => null,
]);
}
/**
* options에서 특정 키 값 조회
*/
public function getOption(string $key, mixed $default = null): mixed
{
return data_get($this->options, $key, $default);
}
/**
* options에 특정 키 값 설정
*/
public function setOption(string $key, mixed $value): static
{
$options = $this->options ?? [];
data_set($options, $key, $value);
$this->options = $options;
return $this;
}
}