'array', 'settings' => 'array', 'last_activity_at' => 'datetime', 'total_revenue' => 'decimal:2', 'settings_updated_at' => 'datetime', 'subscription_expires_at' => 'datetime', ]; public function events(): HasMany { return $this->hasMany(Event::class); } public function photos(): HasManyThrough { return $this->hasManyThrough( Photo::class, Event::class, 'tenant_id', 'event_id', 'id', 'id' ); } public function purchases(): HasMany { return $this->hasMany(PackagePurchase::class); } public function tenantPackages(): HasMany { return $this->hasMany(TenantPackage::class); } public function packages(): BelongsToMany { return $this->belongsToMany(Package::class, 'tenant_packages') ->withPivot(['price', 'purchased_at', 'expires_at', 'active']) ->withTimestamps(); } public function activeResellerPackage(): HasOne { return $this->hasOne(TenantPackage::class)->where('active', true); } public function canCreateEvent(): bool { return $this->hasEventAllowance(); } public function incrementUsedEvents(int $amount = 1): bool { $package = $this->getActiveResellerPackage(); if (! $package) { return false; } $package->increment('used_events', $amount); return true; } public function setSettingsAttribute($value): void { if (is_string($value)) { $this->attributes['settings'] = $value; return; } $this->attributes['settings'] = json_encode($value ?? []); } public function incrementCredits(int $amount, string $reason = 'manual', ?string $note = null, ?int $purchaseId = null): bool { if ($amount <= 0) { return false; } $balance = (int) ($this->event_credits_balance ?? 0) + $amount; $this->forceFill(['event_credits_balance' => $balance])->save(); EventCreditsLedger::create([ 'tenant_id' => $this->id, 'delta' => $amount, 'reason' => $reason, 'related_purchase_id' => $purchaseId, 'note' => $note, ]); Log::info('Tenant credits incremented', [ 'tenant_id' => $this->id, 'delta' => $amount, 'reason' => $reason, 'purchase_id' => $purchaseId, ]); return true; } public function decrementCredits(int $amount, string $reason = 'usage', ?string $note = null, ?int $purchaseId = null): bool { $current = (int) ($this->event_credits_balance ?? 0); if ($amount <= 0 || $amount > $current) { return false; } $balance = $current - $amount; $this->forceFill(['event_credits_balance' => $balance])->save(); EventCreditsLedger::create([ 'tenant_id' => $this->id, 'delta' => -$amount, 'reason' => $reason, 'related_purchase_id' => $purchaseId, 'note' => $note, ]); Log::info('Tenant credits decremented', [ 'tenant_id' => $this->id, 'delta' => -$amount, 'reason' => $reason, 'purchase_id' => $purchaseId, ]); return true; } public function hasEventAllowance(): bool { $package = $this->getActiveResellerPackage(); if ($package && $package->canCreateEvent()) { return true; } return (int) ($this->event_credits_balance ?? 0) > 0; } public function consumeEventAllowance(int $amount = 1, string $reason = 'event.create', ?string $note = null): bool { $package = $this->getActiveResellerPackage(); if ($package && $package->canCreateEvent()) { $package->increment('used_events', $amount); Log::info('Tenant package usage recorded', [ 'tenant_id' => $this->id, 'tenant_package_id' => $package->id, 'used_events' => $package->used_events, 'amount' => $amount, ]); return true; } return $this->decrementCredits($amount, $reason, $note); } public function getActiveResellerPackage(): ?TenantPackage { return $this->activeResellerPackage() ->whereHas('package', fn ($query) => $query->where('type', 'reseller')) ->where('active', true) ->orderByDesc('expires_at') ->first(); } public function activeSubscription(): Attribute { return Attribute::make( get: fn () => $this->activeResellerPackage()->exists(), ); } public function user(): BelongsTo { return $this->belongsTo(User::class); } }