php-tenant/Models/Feature.php
Claude 126d454bba
chore: add IDE helper annotations to Eloquent models
Add @property, @property-read, @method, and @mixin PHPDoc annotations
to the seven core Eloquent models for IDE autocompletion support.

Models annotated:
- Workspace: all columns, relationships, scopes (active, ordered)
- User: all columns, relationships, factory
- WorkspaceMember: relationship props, scope methods (forWorkspace, forUser, withRole, inTeam, owners)
- WorkspaceInvitation: all columns, relationships, scopes (pending, expired, accepted)
- Namespace_: all columns, relationships, scopes (active, ordered, ownedByUser, ownedByWorkspace, accessibleBy)
- Package: all columns, relationships, scopes (active, public, base, addons, purchasable, free, ordered)
- Feature: all columns, relationships, scopes (active, inCategory, root)

Fixes #31

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:55:50 +00:00

190 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* Feature model - entitlement feature definition (e.g. social.accounts, bio.pages).
*
* @property int $id
* @property string $code
* @property string $name
* @property string|null $description
* @property string|null $category
* @property string $type
* @property string $reset_type
* @property int|null $rolling_window_days
* @property int|null $parent_feature_id
* @property int $sort_order
* @property bool $is_active
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, Package> $packages
* @property-read Feature|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, Feature> $children
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature active()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature inCategory(string $category)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature root()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature query()
*
* @mixin \Eloquent
*/
class Feature extends Model
{
use HasFactory;
protected $table = 'entitlement_features';
protected $fillable = [
'code',
'name',
'description',
'category',
'type',
'reset_type',
'rolling_window_days',
'parent_feature_id',
'sort_order',
'is_active',
];
protected $casts = [
'rolling_window_days' => 'integer',
'sort_order' => 'integer',
'is_active' => 'boolean',
];
/**
* Feature types.
*/
public const TYPE_BOOLEAN = 'boolean';
public const TYPE_LIMIT = 'limit';
public const TYPE_UNLIMITED = 'unlimited';
/**
* Reset types.
*/
public const RESET_NONE = 'none';
public const RESET_MONTHLY = 'monthly';
public const RESET_ROLLING = 'rolling';
/**
* Packages that include this feature.
*/
public function packages(): BelongsToMany
{
return $this->belongsToMany(Package::class, 'entitlement_package_features', 'feature_id', 'package_id')
->withPivot('limit_value')
->withTimestamps();
}
/**
* Parent feature (for hierarchical limits / global pools).
*/
public function parent(): BelongsTo
{
return $this->belongsTo(Feature::class, 'parent_feature_id');
}
/**
* Child features (allowances within a global pool).
*/
public function children(): HasMany
{
return $this->hasMany(Feature::class, 'parent_feature_id');
}
/**
* Scope to active features.
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* Scope to features in a category.
*/
public function scopeInCategory($query, string $category)
{
return $query->where('category', $category);
}
/**
* Scope to root features (no parent).
*/
public function scopeRoot($query)
{
return $query->whereNull('parent_feature_id');
}
/**
* Check if this feature is a boolean toggle.
*/
public function isBoolean(): bool
{
return $this->type === self::TYPE_BOOLEAN;
}
/**
* Check if this feature has a usage limit.
*/
public function hasLimit(): bool
{
return $this->type === self::TYPE_LIMIT;
}
/**
* Check if this feature is unlimited.
*/
public function isUnlimited(): bool
{
return $this->type === self::TYPE_UNLIMITED;
}
/**
* Check if this feature resets monthly.
*/
public function resetsMonthly(): bool
{
return $this->reset_type === self::RESET_MONTHLY;
}
/**
* Check if this feature uses rolling window reset.
*/
public function resetsRolling(): bool
{
return $this->reset_type === self::RESET_ROLLING;
}
/**
* Check if this is a child feature (part of a global pool).
*/
public function isChildFeature(): bool
{
return $this->parent_feature_id !== null;
}
/**
* Get the global pool feature code (parent or self).
*/
public function getPoolFeatureCode(): string
{
return $this->parent?->code ?? $this->code;
}
}