'boolean', 'date_of_birth' => 'date', 'email_verified_at' => 'datetime', 'inactive_at' => 'datetime', 'deleted_at' => 'datetime', ]; // protected $guard_name = 'web'; /** * Get the index name for the model. * * @return string */ public function searchableAs() { return 'organizations_index'; } /** * Convert this model to a searchable array. * * @return array */ public function toSearchableArray() { // Prepare eager loaded relationships $this->load( 'languages', 'locations.district.translations', 'locations.city.translations', 'locations.division.translations', 'locations.country.translations', 'tags.contexts.tags', 'tags.contexts.tags.locale', 'tags.contexts.category.ancestorsAndSelf', ); return [ 'id' => $this->id, 'name' => $this->name, //TODO: Update to multilang database structure in future 'about_nl' => $this->about, 'about_en' => $this->about, 'about_fr' => $this->about, 'about_de' => $this->about, 'about_es' => $this->about, 'about_short_nl' => $this->about_short, 'about_short_en' => $this->about_short, 'about_short_fr' => $this->about_short, 'about_short_de' => $this->about_short, 'about_short_es' => $this->about_short, 'motivation_nl' => $this->motivation, 'motivation_en' => $this->motivation, 'motivation_fr' => $this->motivation, 'motivation_de' => $this->motivation, 'motivation_es' => $this->motivation, 'cyclos_skills' => $this->cyclos_skills, // Legacy column, will not be used in the future 'website' => $this->website, 'last_login_at' => $this->last_login_at, 'lang_preference' => $this->lang_preference, 'languages' => $this->languages->map(function ($language) { // map() as languages is a collection return [ 'id' => $language->id, 'name' => $language->name, 'lang_code' => $language->lang_code, ]; }), 'locations' => $this->locations->map(function ($location) { // map() as locations is a collection return [ 'id' => $location->id, 'district' => $location->district ? $location->district->translations->map(function ($translation) { // map() as translations is a collection return $translation->name; })->toArray() : [], 'city' => $location->city ? $location->city->translations->map(function ($translation) { // map() as translations is a collection return $translation->name; })->toArray() : [], 'division' => $location->division ? $location->division->translations->map(function ($translation) { // map() as translations is a collection return $translation->name; })->toArray() : [], 'country' => $location->country ? $location->country->translations->map(function ($translation) { // map() as translations is a collection return $translation->name; })->toArray() : [], ]; }), 'tags' => $this->tags->map(function ($tag) { return [ 'contexts' => $tag->contexts ->map(function ($context) { return [ 'tags' => $context->tags->map(function ($tag) { // Include the locale in the field name for tags return [ 'name_' . $tag->locale->locale => StringHelper::DutchTitleCase($tag->normalized), ]; }), 'categories' => Category::with(['translations' => function ($query) { $query->select('category_id', 'locale', 'name');}]) ->find([ $context->category->ancestorsAndSelf()->get()->flatMap(function ($related) { $categoryPath = explode('.', $related->path); return $categoryPath; }) ->unique()->values()->toArray() ])->map(function ($category) { // Include the locale in the field name for categories return $category->translations->mapWithKeys(function ($translation) { return ['name_' . $translation->locale => StringHelper::DutchTitleCase($translation->name)]; }); }), ]; }), ]; }) ]; } /** * Get the organization's profile. * One-to-one */ public function profile() { return $this->hasOne(Profile::class); } /** * Get all of the organization's accounts. * One-to-many polymorphic. * * @return void */ public function accounts() { return $this->morphMany(Account::class, 'accountable'); } /** * Check if the organization has any associated accounts. * * @return bool Returns true if the user has accounts, false otherwise. */ public function hasAccounts() { $accountsExists = DB::table('accounts') ->where('accountable_id', $this->id) ->where('accountable_type', 'App\Models\User') ->exists(); return $accountsExists; } /** * Get the profile's bank(s) that it can manage. * Many-to-many. */ public function banksManaged() { return $this->belongsToMany(Bank::class); } public function banksClient() { return $this->morphToMany(Bank::class, 'client', 'bank_clients') ->wherePivot('relationship_type', 'local'); } public function attachBankClient($bank, $relationshipType = null) { // Set default relationship type if not provided $relationshipType = $relationshipType ?? 'local'; // Accept either ID or Bank model $bankId = $bank instanceof Bank ? $bank->id : $bank; // Verify existence if numeric ID was provided if (is_numeric($bankId)) { if (!Bank::where('id', $bankId)->exists()) { throw new \Exception("Bank with ID {$bankId} does not exist"); } } $this->banksClient()->sync([$bankId => [ 'relationship_type' => $relationshipType, 'created_at' => now(), 'updated_at' => now() ]]); return $this; } public function detachBankClient($bank, $relationshipType = null) { // Set default relationship type if not provided $relationshipType = $relationshipType ?? 'local'; // Accept either ID or Bank model $bankId = $bank instanceof Bank ? $bank->id : $bank; // Verify existence if numeric ID was provided if (is_numeric($bankId)) { if (!Bank::where('id', $bankId)->exists()) { throw new \Exception("Bank with ID {$bankId} does not exist"); } } // Detach with additional pivot conditions $this->banksClient()->wherePivot('relationship_type', $relationshipType) ->detach($bankId); return $this; } /** * Get the organization's user(s). * Many-to-many. */ // todo: replace instances to managers() as these are actually profile managers and no organization users. See $this-> manangers() public function users() { return $this->belongsToMany(User::class); } /** * Get the organization's profile manager(s). * Many-to-many. */ public function managers() { return $this->belongsToMany(User::class); } /** * Get all of the languages for the organization. * Many-to-many polymorphic. */ public function languages() { return $this->morphToMany(Language::class, 'languagable')->withPivot('competence'); } /** * Get all of the social for the organization. * Many-to-many polymorphic. */ public function socials() { return $this->morphToMany(Social::class, 'sociable')->withPivot('id', 'user_on_social', 'server_of_social'); } /** * Get all related the locations of the organization. * One-to-many polymorph. */ public function locations() { return $this->morphMany(Location::class, 'locatable'); } /** * Get all of the organization's message settings. */ public function message_settings() { return $this->morphMany(MessageSetting::class, 'message_settingable'); } /** * Get all of the Organization's posts. */ public function posts() { return $this->morphMany(Post::class, 'postable'); } /** * Get all of the Organization's posts. */ public function categories() { return $this->morphMany(Category::class, 'categoryable'); } public function sendEmailVerificationNotification() { \Mail::to($this->email)->send(new \App\Mail\VerifyProfileEmailMailable($this)); } /** * Configure the activity log options for the User model. * * This method sets up the logging to: * - Only log changes to the 'name', 'password', and 'last_login_ip' attributes. * - Log only when these attributes have been modified (dirty). * - Prevent submission of empty logs. * - Use 'user' as the log name. * * @return \Spatie\Activitylog\LogOptions */ public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() ->logOnly([ 'name', 'last_login_ip', 'inactive_at', 'deleted_at', ]) ->logOnlyDirty() // Only log attributes that have been changed ->dontSubmitEmptyLogs() ->useLogName('Organization'); } /** * Wirechat: Returns the URL for the user's cover image (avatar). * Adjust the 'avatar_url' field to your database setup. */ public function getCoverUrlAttribute(): ?string { return Storage::url($this->profile_photo_path) ?? null; } /** * Wirechat:Returns the URL for the user's profile page. * Adjust the 'profile' route as needed for your setup. */ public function getProfileUrlAttribute(): ?string { return route('profile.show_by_type_and_id', ['type' => __('organization'), 'id' => $this->id]); } /** * Wirechat: Returns the display name for the user. * Modify this to use your preferred name field. */ public function getDisplayNameAttribute(): ?string { if ($this->full_name !== $this->name) { return $this->full_name . ' (' . $this->name . ')'; } elseif ($this->name) { return $this->name; } else { return __('Organization'); } } /** * Wirechat: Search for users when creating a new chat or adding members to a group. * Customize the search logic to limit results, such as restricting to friends or eligible users only. */ public function searchChatables(string $query): ?\Illuminate\Support\Collection { $searchableFields = ['name', 'full_name']; // Search across all profile types $users = \App\Models\User::where(function ($queryBuilder) use ($searchableFields, $query) { foreach ($searchableFields as $field) { $queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%'); } })->limit(6)->get(); $organizations = Organization::where(function ($queryBuilder) use ($searchableFields, $query) { foreach ($searchableFields as $field) { $queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%'); } })->limit(6)->get(); $banks = \App\Models\Bank::where(function ($queryBuilder) use ($searchableFields, $query) { foreach ($searchableFields as $field) { $queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%'); } })->limit(6)->get(); $admins = \App\Models\Admin::where(function ($queryBuilder) use ($searchableFields, $query) { foreach ($searchableFields as $field) { $queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%'); } })->limit(6)->get(); // Combine all results into a base Collection to avoid serialization issues $results = collect() ->merge($users->all()) ->merge($organizations->all()) ->merge($banks->all()) ->merge($admins->all()); // Filter out profiles based on configuration return $results->filter(function ($profile) { // Check inactive profiles if (timebank_config('profile_inactive.messenger_hidden') && !$profile->isActive()) { return false; } // Check email unverified profiles if (timebank_config('profile_email_unverified.messenger_hidden') && !$profile->isEmailVerified()) { return false; } // Check incomplete profiles if ( timebank_config('profile_incomplete.messenger_hidden') && method_exists($profile, 'hasIncompleteProfile') && $profile->hasIncompleteProfile($profile) ) { return false; } return true; })->take(6)->values(); } /** * Wirechat: Determine if the user can create new groups. */ public function canCreateGroups(): bool { return timebank_config('profiles.organization.messenger_can_create_groups'); } /** * Check if the Organization has any transactions with another model (User, Organization, Bank). * * @param \Illuminate\Database\Eloquent\Model $model * @return bool */ public function hasTransactionsWith($model): bool { $modelClass = get_class($model); $modelId = $model->id; // Check if this Organization is involved in any transaction where the other model is either the sender or receiver return Transaction::where(function ($query) use ($modelClass, $modelId) { $query->whereHas('accountFrom.accountable', function ($q) use ($modelClass, $modelId) { $q->where('accountable_type', $modelClass) ->where('accountable_id', $modelId); }) ->orWhereHas('accountTo.accountable', function ($q) use ($modelClass, $modelId) { $q->where('accountable_type', $modelClass) ->where('accountable_id', $modelId); }); }) ->where(function ($query) { $query->whereHas('accountFrom.accountable', function ($q) { $q->where('accountable_type', Organization::class) ->where('accountable_id', $this->id); }) ->orWhereHas('accountTo.accountable', function ($q) { $q->where('accountable_type', Organization::class) ->where('accountable_id', $this->id); }); }) ->exists(); } /** * Get the message settings for this organization */ public function messageSettings() { return $this->morphMany(MessageSetting::class, 'message_settingable'); } }