hasMany(self::class, 'parent_category_id'); } public function custom_fields() { return $this->hasMany(CustomFieldCategory::class); } public function getImageAttribute($image) { if (! empty($image)) { return url(Storage::url($image)); } return $image; } public function items() { return $this->hasMany(Item::class); } public function approved_items() { return $this->hasMany(Item::class)->where('status', 'approved'); } public function getAllItemsCountAttribute() { // Count items in this category $totalItems = $this->items()->where('status', 'approved')->getNonExpiredItems()->count(); // Count items from ALL descendants (not just loaded ones) using recursive query $descendantIds = $this->descendants() ->where('status', 1) ->pluck('id') ->toArray(); if (! empty($descendantIds)) { $descendantItemsCount = Item::without('translations') ->whereIn('category_id', $descendantIds) ->where('status', 'approved') ->getNonExpiredItems() ->count(); $totalItems += $descendantItemsCount; } return $totalItems; } public function scopeSearch($query, $search) { $search = '%'.$search.'%'; return $query->where(function ($q) use ($search) { $q->orWhere('name', 'LIKE', $search) ->orWhere('description', 'LIKE', $search) ->orWhereHas('translations', function ($q) use ($search) { $q->where('description', 'LIKE', $search); }); }); } public function slider(): MorphOne { return $this->morphOne(Slider::class, 'model'); } public function translations() { return $this->hasMany(CategoryTranslation::class); } public function getTranslatedNameAttribute() { $languageCode = request()->header('Content-Language') ?? app()->getLocale(); if (! empty($languageCode)) { // NOTE : This code can be done in Cache $language = Language::select(['id', 'code'])->where('code', $languageCode)->first(); if (empty($language)) { return $this->name; } $languageId = $language->id; $translation = $this->translations->first(static function ($data) use ($languageId) { return $data->language_id == $languageId; }); return ! empty($translation?->name) ? $translation->name : $this->name; } return $this->name; } public function getTranslatedDescriptionAttribute() { $languageCode = request()->header('Content-Language') ?? app()->getLocale(); if (! empty($languageCode)) { // NOTE : This code can be done in Cache $language = Language::select(['id', 'code'])->where('code', $languageCode)->first(); if (empty($language)) { return $this->description; } $languageId = $language->id; $translation = $this->translations->first(static function ($data) use ($languageId) { return $data->language_id == $languageId; }); return ! empty($translation?->description) ? $translation->description : $this->description; } return $this->description; } public function parent() { return $this->belongsTo(Category::class, 'parent_category_id'); } public function getFullPathAttribute() { $names = []; $current = $this; $visited = []; while ($current) { if (in_array($current->id, $visited, true)) { break; // prevent loop } $visited[] = $current->id; $names[] = $current->name; $current = $current->parent; } return implode(' > ', array_reverse($names)); } public function getItemsGroupedByStatusAttribute() { $counts = []; // Count items in this category $items = $this->items()->get(); foreach ($items as $item) { $counts[$item->status] = ($counts[$item->status] ?? 0) + 1; } // Include subcategories recursively foreach ($this->subcategories as $subcategory) { $subCounts = $subcategory->items_grouped_by_status; foreach ($subCounts as $status => $count) { $counts[$status] = ($counts[$status] ?? 0) + $count; } } return $counts; } public function getOtherItemsCountAttribute() { $totalItems = $this->items()->where('status', '!=', 'approved')->count(); foreach ($this->subcategories as $subcategory) { $totalItems += $subcategory->other_items_count; } return $totalItems; } }