'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; } }