257 lines
5.9 KiB
PHP
257 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;
|
|
}
|
|
}
|