'int', 'user_id' => 'int', 'pay_year' => 'int', 'pay_month' => 'int', 'gross_amount' => 'decimal:0', 'income_tax' => 'decimal:0', 'local_income_tax' => 'decimal:0', 'total_deductions' => 'decimal:0', 'net_amount' => 'decimal:0', 'payment_date' => 'date', 'confirmed_at' => 'datetime', 'paid_at' => 'datetime', ]; protected $attributes = [ 'status' => 'draft', ]; // ========================================================================= // 상수 // ========================================================================= public const STATUS_DRAFT = 'draft'; public const STATUS_CONFIRMED = 'confirmed'; public const STATUS_PAID = 'paid'; public const STATUS_MAP = [ 'draft' => '작성중', 'confirmed' => '확정', 'paid' => '지급완료', ]; public const STATUS_COLORS = [ 'draft' => 'amber', 'confirmed' => 'blue', 'paid' => 'emerald', ]; /** 소득세율 3% */ public const INCOME_TAX_RATE = 0.03; /** 지방소득세율 0.3% */ public const LOCAL_TAX_RATE = 0.003; // ========================================================================= // 관계 정의 // ========================================================================= public function user(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } public function confirmer(): BelongsTo { return $this->belongsTo(User::class, 'confirmed_by'); } public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } // ========================================================================= // Accessor // ========================================================================= public function getStatusLabelAttribute(): string { return self::STATUS_MAP[$this->status] ?? $this->status; } public function getStatusColorAttribute(): string { return self::STATUS_COLORS[$this->status] ?? 'gray'; } public function getPeriodLabelAttribute(): string { return sprintf('%d년 %d월', $this->pay_year, $this->pay_month); } // ========================================================================= // 상태 헬퍼 // ========================================================================= public function isEditable(): bool { return $this->status === self::STATUS_DRAFT; } public function isConfirmable(): bool { return $this->status === self::STATUS_DRAFT; } public function isPayable(): bool { return $this->status === self::STATUS_CONFIRMED; } public function isDeletable(): bool { return $this->status === self::STATUS_DRAFT; } // ========================================================================= // 세금 계산 (정적) // ========================================================================= /** * 지급총액으로 세금/공제/실지급액 계산 * * @return array{income_tax: int, local_income_tax: int, total_deductions: int, net_amount: int} */ public static function calculateTax(float $grossAmount): array { $incomeTax = (int) (floor($grossAmount * self::INCOME_TAX_RATE / 10) * 10); $localIncomeTax = (int) (floor($grossAmount * self::LOCAL_TAX_RATE / 10) * 10); $totalDeductions = $incomeTax + $localIncomeTax; $netAmount = (int) max(0, $grossAmount - $totalDeductions); return [ 'income_tax' => $incomeTax, 'local_income_tax' => $localIncomeTax, 'total_deductions' => $totalDeductions, 'net_amount' => $netAmount, ]; } // ========================================================================= // 스코프 // ========================================================================= public function scopeForTenant($query, ?int $tenantId = null) { $tenantId = $tenantId ?? session('selected_tenant_id', 1); return $query->where($this->table.'.tenant_id', $tenantId); } public function scopeForPeriod($query, int $year, int $month) { return $query->where('pay_year', $year)->where('pay_month', $month); } public function scopeWithStatus($query, string $status) { return $query->where('status', $status); } }