328 lines
7.9 KiB
PHP
328 lines
7.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 FundSchedule extends Model
|
|
{
|
|
use BelongsToTenant, ModelTrait, SoftDeletes;
|
|
|
|
// 일정 유형 상수
|
|
public const TYPE_INCOME = 'income';
|
|
|
|
public const TYPE_EXPENSE = 'expense';
|
|
|
|
// 상태 상수
|
|
public const STATUS_PENDING = 'pending';
|
|
|
|
public const STATUS_COMPLETED = 'completed';
|
|
|
|
public const STATUS_CANCELLED = 'cancelled';
|
|
|
|
// 반복 규칙 상수
|
|
public const RECURRENCE_DAILY = 'daily';
|
|
|
|
public const RECURRENCE_WEEKLY = 'weekly';
|
|
|
|
public const RECURRENCE_MONTHLY = 'monthly';
|
|
|
|
public const RECURRENCE_YEARLY = 'yearly';
|
|
|
|
protected $fillable = [
|
|
'tenant_id',
|
|
'title',
|
|
'description',
|
|
'schedule_type',
|
|
'scheduled_date',
|
|
'amount',
|
|
'currency',
|
|
'related_bank_account_id',
|
|
'counterparty',
|
|
'category',
|
|
'status',
|
|
'is_recurring',
|
|
'recurrence_rule',
|
|
'recurrence_end_date',
|
|
'completed_date',
|
|
'completed_amount',
|
|
'memo',
|
|
'created_by',
|
|
'updated_by',
|
|
'deleted_by',
|
|
];
|
|
|
|
protected $hidden = [
|
|
'created_by',
|
|
'updated_by',
|
|
'deleted_by',
|
|
'deleted_at',
|
|
];
|
|
|
|
protected $casts = [
|
|
'amount' => 'decimal:2',
|
|
'completed_amount' => 'decimal:2',
|
|
'scheduled_date' => 'date:Y-m-d',
|
|
'completed_date' => 'date:Y-m-d',
|
|
'recurrence_end_date' => 'date:Y-m-d',
|
|
'is_recurring' => 'boolean',
|
|
];
|
|
|
|
// ============================================================
|
|
// 관계 정의
|
|
// ============================================================
|
|
|
|
/**
|
|
* 테넌트
|
|
*/
|
|
public function tenant(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Tenant::class);
|
|
}
|
|
|
|
/**
|
|
* 관련 은행 계좌
|
|
*/
|
|
public function bankAccount(): BelongsTo
|
|
{
|
|
return $this->belongsTo(BankAccount::class, 'related_bank_account_id');
|
|
}
|
|
|
|
// ============================================================
|
|
// Accessors
|
|
// ============================================================
|
|
|
|
/**
|
|
* 포맷된 금액
|
|
*/
|
|
public function getFormattedAmountAttribute(): string
|
|
{
|
|
$amount = abs($this->amount);
|
|
|
|
if ($amount >= 100000000) {
|
|
return number_format($amount / 100000000, 1).'억원';
|
|
} elseif ($amount >= 10000000) {
|
|
return number_format($amount / 10000000, 1).'천만원';
|
|
} elseif ($amount >= 10000) {
|
|
return number_format($amount / 10000, 0).'만원';
|
|
}
|
|
|
|
return number_format($amount).'원';
|
|
}
|
|
|
|
/**
|
|
* 일정 유형 라벨
|
|
*/
|
|
public function getTypeLabelAttribute(): string
|
|
{
|
|
return match ($this->schedule_type) {
|
|
self::TYPE_INCOME => '입금 예정',
|
|
self::TYPE_EXPENSE => '지급 예정',
|
|
default => '기타',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 상태 라벨
|
|
*/
|
|
public function getStatusLabelAttribute(): string
|
|
{
|
|
return match ($this->status) {
|
|
self::STATUS_PENDING => '예정',
|
|
self::STATUS_COMPLETED => '완료',
|
|
self::STATUS_CANCELLED => '취소',
|
|
default => '미정',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 일정 유형별 색상 클래스
|
|
*/
|
|
public function getTypeColorClassAttribute(): string
|
|
{
|
|
return match ($this->schedule_type) {
|
|
self::TYPE_INCOME => 'bg-green-100 text-green-800 border-green-200',
|
|
self::TYPE_EXPENSE => 'bg-red-100 text-red-800 border-red-200',
|
|
default => 'bg-gray-100 text-gray-800 border-gray-200',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 상태별 색상 클래스
|
|
*/
|
|
public function getStatusColorClassAttribute(): string
|
|
{
|
|
return match ($this->status) {
|
|
self::STATUS_PENDING => 'bg-yellow-100 text-yellow-800',
|
|
self::STATUS_COMPLETED => 'bg-green-100 text-green-800',
|
|
self::STATUS_CANCELLED => 'bg-gray-100 text-gray-500',
|
|
default => 'bg-gray-100 text-gray-800',
|
|
};
|
|
}
|
|
|
|
// ============================================================
|
|
// Scopes
|
|
// ============================================================
|
|
|
|
/**
|
|
* 입금 예정만
|
|
*/
|
|
public function scopeIncome($query)
|
|
{
|
|
return $query->where('schedule_type', self::TYPE_INCOME);
|
|
}
|
|
|
|
/**
|
|
* 지급 예정만
|
|
*/
|
|
public function scopeExpense($query)
|
|
{
|
|
return $query->where('schedule_type', self::TYPE_EXPENSE);
|
|
}
|
|
|
|
/**
|
|
* 예정 상태만
|
|
*/
|
|
public function scopePending($query)
|
|
{
|
|
return $query->where('status', self::STATUS_PENDING);
|
|
}
|
|
|
|
/**
|
|
* 완료 상태만
|
|
*/
|
|
public function scopeCompleted($query)
|
|
{
|
|
return $query->where('status', self::STATUS_COMPLETED);
|
|
}
|
|
|
|
/**
|
|
* 특정 월의 일정
|
|
*/
|
|
public function scopeForMonth($query, int $year, int $month)
|
|
{
|
|
return $query->whereYear('scheduled_date', $year)
|
|
->whereMonth('scheduled_date', $month);
|
|
}
|
|
|
|
/**
|
|
* 날짜 범위 필터
|
|
*/
|
|
public function scopeDateBetween($query, string $startDate, string $endDate)
|
|
{
|
|
return $query->whereBetween('scheduled_date', [$startDate, $endDate]);
|
|
}
|
|
|
|
/**
|
|
* 정렬 (예정일 기준)
|
|
*/
|
|
public function scopeOrdered($query)
|
|
{
|
|
return $query->orderBy('scheduled_date')->orderBy('id');
|
|
}
|
|
|
|
// ============================================================
|
|
// 메서드
|
|
// ============================================================
|
|
|
|
/**
|
|
* 입금 예정인지 확인
|
|
*/
|
|
public function isIncome(): bool
|
|
{
|
|
return $this->schedule_type === self::TYPE_INCOME;
|
|
}
|
|
|
|
/**
|
|
* 지급 예정인지 확인
|
|
*/
|
|
public function isExpense(): bool
|
|
{
|
|
return $this->schedule_type === self::TYPE_EXPENSE;
|
|
}
|
|
|
|
/**
|
|
* 예정 상태인지 확인
|
|
*/
|
|
public function isPending(): bool
|
|
{
|
|
return $this->status === self::STATUS_PENDING;
|
|
}
|
|
|
|
/**
|
|
* 완료 상태인지 확인
|
|
*/
|
|
public function isCompleted(): bool
|
|
{
|
|
return $this->status === self::STATUS_COMPLETED;
|
|
}
|
|
|
|
/**
|
|
* 완료 처리
|
|
*/
|
|
public function markAsCompleted(?float $actualAmount = null, ?string $completedDate = null): void
|
|
{
|
|
$this->update([
|
|
'status' => self::STATUS_COMPLETED,
|
|
'completed_date' => $completedDate ?? now()->toDateString(),
|
|
'completed_amount' => $actualAmount ?? $this->amount,
|
|
'updated_by' => auth()->id(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 취소 처리
|
|
*/
|
|
public function markAsCancelled(): void
|
|
{
|
|
$this->update([
|
|
'status' => self::STATUS_CANCELLED,
|
|
'updated_by' => auth()->id(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 일정 유형 목록
|
|
*/
|
|
public static function getTypeOptions(): array
|
|
{
|
|
return [
|
|
self::TYPE_INCOME => '입금 예정',
|
|
self::TYPE_EXPENSE => '지급 예정',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 상태 목록
|
|
*/
|
|
public static function getStatusOptions(): array
|
|
{
|
|
return [
|
|
self::STATUS_PENDING => '예정',
|
|
self::STATUS_COMPLETED => '완료',
|
|
self::STATUS_CANCELLED => '취소',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 반복 규칙 목록
|
|
*/
|
|
public static function getRecurrenceOptions(): array
|
|
{
|
|
return [
|
|
self::RECURRENCE_DAILY => '매일',
|
|
self::RECURRENCE_WEEKLY => '매주',
|
|
self::RECURRENCE_MONTHLY => '매월',
|
|
self::RECURRENCE_YEARLY => '매년',
|
|
];
|
|
}
|
|
}
|