'datetime', 'expires_at' => 'datetime', 'last_paid_at' => 'datetime', 'max_users' => 'integer', 'options' => 'array', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', ]; protected $hidden = [ 'deleted_at', ]; /** * 활성화된 테넌트만 조회하는 스코프 */ public function scopeActive($query) { return $query->whereIn('tenant_st_code', ['trial', 'active']); } /** * 테넌트가 활성 상태인지 확인 */ public function isActive(): bool { return in_array($this->tenant_st_code, ['trial', 'active']); } /** * 테넌트가 트라이얼 상태인지 확인 */ public function isTrial(): bool { return $this->tenant_st_code === 'trial'; } // 관계 정의 (예시) public function plan() { return $this->belongsTo(Plan::class, 'plan_id'); } public function subscription() { return $this->belongsTo(Subscription::class, 'subscription_id'); } public function userTenants() { return $this->hasMany(UserTenant::class); } public function users() { return $this->belongsToMany(User::class, 'user_tenants'); } public function roles() { return $this->hasMany(Role::class); } public function userRoles() { return $this->hasMany(UserRole::class); } public function files() { return $this->morphMany(File::class, 'fileable'); } /** * Get storage usage percentage */ public function getStorageUsagePercentage(): float { if (! $this->storage_limit || $this->storage_limit == 0) { return 0; } return ($this->storage_used / $this->storage_limit) * 100; } /** * Check if storage is near limit (90%) */ public function isStorageNearLimit(): bool { return $this->getStorageUsagePercentage() >= 90; } /** * Check if storage quota exceeded */ public function isStorageExceeded(): bool { return $this->storage_used > $this->storage_limit; } /** * Check if in grace period */ public function isInGracePeriod(): bool { return $this->storage_grace_period_until && now()->lessThan($this->storage_grace_period_until); } /** * Check if upload is allowed */ public function canUpload(int $fileSize = 0): array { $newUsage = $this->storage_used + $fileSize; // Check if exceeds limit if ($newUsage > $this->storage_limit) { // Check grace period if ($this->isInGracePeriod()) { return [ 'allowed' => true, 'warning' => true, 'message' => __('file.storage_exceeded_grace_period', [ 'until' => $this->storage_grace_period_until->format('Y-m-d'), ]), ]; } // Grace period expired - block upload return [ 'allowed' => false, 'message' => __('file.storage_quota_exceeded'), ]; } // Check if near limit (90%) $percentage = ($newUsage / $this->storage_limit) * 100; if ($percentage >= 90 && ! $this->storage_warning_sent_at) { // Send warning (once) $this->update([ 'storage_warning_sent_at' => now(), 'storage_grace_period_until' => now()->addDays(7), ]); // TODO: Dispatch email notification // dispatch(new SendStorageWarningEmail($this)); } return ['allowed' => true]; } /** * Increment storage usage */ public function incrementStorage(int $bytes): void { $this->increment('storage_used', $bytes); } /** * Decrement storage usage */ public function decrementStorage(int $bytes): void { $this->decrement('storage_used', max(0, $bytes)); } /** * Get human-readable storage used */ public function getStorageUsedFormatted(): string { return $this->formatBytes($this->storage_used); } /** * Get human-readable storage limit */ public function getStorageLimitFormatted(): string { return $this->formatBytes($this->storage_limit); } /** * Format bytes to human-readable string */ private function formatBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, 2).' '.$units[$pow]; } }