'required|max:255|string|unique_undeleted', 'notes' => 'string|nullable', 'deployable' => 'required', 'pending' => 'required', 'archived' => 'required', ]; protected $fillable = [ 'archived', 'deployable', 'name', 'notes', 'pending', ]; use Searchable; /** * Per-request memo of the ID lists used by Asset's status-driven scopes * (RTD / Pending / Undeployable / Archived / NotArchived). Each scope * fired a separate `SELECT id FROM status_labels WHERE …` query on every * call before this was introduced, which under withCount + transformer * loops (see AssetModelsTransformer) added dozens of redundant queries * per page or API hit. * * Cleared on save/delete via model events below, and reset between tests * in InitializesSettings (alongside Setting::$_cache). */ protected static array $statusIdCache = []; /** * Return the cached set of status_label IDs matching the requested * "kind" — one of: deployable, pending, undeployable, archived, * not_archived. Each call after the first in a single request reads * from memory. */ public static function idsFor(string $type): Collection { return self::$statusIdCache[$type] ??= match ($type) { 'deployable' => self::where('deployable', 1)->where('pending', 0)->where('archived', 0)->whereNull('deleted_at')->pluck('id'), 'pending' => self::where('deployable', 0)->where('pending', 1)->where('archived', 0)->whereNull('deleted_at')->pluck('id'), 'undeployable' => self::where('deployable', 0)->where('pending', 0)->where('archived', 0)->whereNull('deleted_at')->pluck('id'), 'archived' => self::where('deployable', 0)->where('pending', 0)->where('archived', 1)->whereNull('deleted_at')->pluck('id'), 'not_archived' => self::where('archived', 0)->whereNull('deleted_at')->pluck('id'), default => throw new \InvalidArgumentException('Unknown status type: '.$type), }; } public static function clearIdCache(): void { self::$statusIdCache = []; } protected static function booted(): void { // Any mutation to a status label invalidates the cached ID lists. $invalidate = fn () => self::clearIdCache(); static::saved($invalidate); static::deleted($invalidate); static::restored($invalidate); } /** * The attributes that should be included when searching the model. * * @var array */ protected $searchableAttributes = ['name', 'notes']; /** * The relations and their attributes that should be included when searching the model. * * @var array */ protected $searchableRelations = []; public function isDeletable() { return Gate::allows('delete', $this) && (($this->assets_count ?? $this->assets()->count()) === 0) && ($this->deleted_at == ''); } /** * Establishes the status label -> assets relationship * * @author A. Gianotto * * @since [v1.0] * * @return Relation */ public function assets() { return $this->hasMany(Asset::class, 'status_id'); } public function adminuser() { return $this->belongsTo(User::class, 'created_by')->withTrashed(); } /** * Gets the status label type * * @author A. Gianotto * * @since [v1.0] * * @return string */ public function getStatuslabelType() { if (($this->pending == '1') && ($this->archived == '0') && ($this->deployable == '0')) { return 'pending'; } elseif (($this->pending == '0') && ($this->archived == '1') && ($this->deployable == '0')) { return 'archived'; } elseif (($this->pending == '0') && ($this->archived == '0') && ($this->deployable == '0')) { return 'undeployable'; } return 'deployable'; } /** * Query builder scope to for pending status types * * @return Builder Modified query builder */ public function scopePending() { return $this->where('pending', '=', 1) ->where('archived', '=', 0) ->where('deployable', '=', 0); } /** * Query builder scope for archived status types * * @return Builder Modified query builder */ public function scopeArchived() { return $this->where('pending', '=', 0) ->where('archived', '=', 1) ->where('deployable', '=', 0); } /** * Query builder scope for deployable status types * * @return Builder Modified query builder */ public function scopeDeployable() { return $this->where('pending', '=', 0) ->where('archived', '=', 0) ->where('deployable', '=', 1); } /** * Query builder scope for undeployable status types * * @return Builder Modified query builder */ public function scopeUndeployable() { return $this->where('pending', '=', 0) ->where('archived', '=', 0) ->where('deployable', '=', 0); } /** * Helper function to determine type attributes * * @author A. Gianotto * * @since [v1.0] * * @return string */ public static function getStatuslabelTypesForDB($type) { $statustype['pending'] = 0; $statustype['deployable'] = 0; $statustype['archived'] = 0; if ($type == 'pending') { $statustype['pending'] = 1; $statustype['deployable'] = 0; $statustype['archived'] = 0; } elseif ($type == 'deployable') { $statustype['pending'] = 0; $statustype['deployable'] = 1; $statustype['archived'] = 0; } elseif ($type == 'archived') { $statustype['pending'] = 0; $statustype['deployable'] = 0; $statustype['archived'] = 1; } return $statustype; } public function scopeOrderByCreatedBy($query, $order) { return $query->leftJoin('users as admin_sort', 'status_labels.created_by', '=', 'admin_sort.id')->select('status_labels.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } }