__('Expire date is required.'), 'till.date' => __('Expire date must be a valid date.'), 'till.after' => __('Expire date must be in the future.'), 'till.before_or_equal'=> __('Expire date exceeds the maximum allowed period.'), ]; } public function getTillMaxDays(): ?int { $activeProfileType = session('activeProfileType'); if ($activeProfileType && $activeProfileType !== \App\Models\User::class) { return timebank_config('calls.till_max_days_non_user'); } return timebank_config('calls.till_max_days'); } protected function rules(): array { $tillMaxDays = $this->getTillMaxDays(); $tillRule = 'required|date|after:today'; if ($tillMaxDays !== null) { $tillRule .= '|before_or_equal:' . now()->addDays($tillMaxDays)->format('Y-m-d'); } return [ 'content' => ['required', 'string', 'max:' . timebank_config('calls.content_max_input', 200)], 'till' => $tillRule, 'tagId' => 'required|integer|exists:taggable_tags,tag_id', 'country' => 'required|integer|exists:countries,id', 'city' => 'nullable|integer|exists:cities,id', 'district'=> 'nullable|integer|exists:districts,id', ]; } public function openModal(): void { $activeProfileType = session('activeProfileType'); if (!$activeProfileType || !in_array($activeProfileType, [ \App\Models\User::class, \App\Models\Organization::class, \App\Models\Bank::class, ])) { abort(403, __('Only platform profiles (User, Organization, Bank) can create Calls.')); } $activeProfileId = session('activeProfileId'); if (!CallCreditService::profileHasCredits($activeProfileType, (int) $activeProfileId)) { $this->showNoCreditsModal = true; return; } $this->reset(['content', 'till', 'tagId', 'country', 'division', 'city', 'district', 'isPublic']); $this->resetValidation(); // Pre-fill expiry date from platform config default $defaultExpiryDays = timebank_config('calls.default_expiry_days'); if ($defaultExpiryDays) { $this->till = now()->addDays($defaultExpiryDays)->format('Y-m-d'); } // Pre-fill location from the callable profile's primary location $profile = $activeProfileType::find(session('activeProfileId')); $location = $profile?->locations()->with(['country', 'division', 'city', 'district'])->first(); if ($location) { $this->country = $location->country_id; $this->division = $location->division_id; $this->city = $location->city_id; $this->district = $location->district_id; } $this->showModal = true; } /** * Called by the CallSkillInput child component when a tag is selected or created. */ public function callTagSelected(int $tagId): void { $this->tagId = $tagId; $this->resetValidation('tagId'); } /** * Called by the CallSkillInput child component when the tag input is cleared. */ public function callTagCleared(): void { $this->tagId = null; } public function countryToParent($value): void { $this->country = $value ?: null; } public function divisionToParent($value): void { $this->division = $value ?: null; } public function cityToParent($value): void { $this->city = $value ?: null; } public function districtToParent($value): void { $this->district = $value ?: null; } public function save(): void { $activeProfileType = session('activeProfileType'); if (!$activeProfileType || !in_array($activeProfileType, [ \App\Models\User::class, \App\Models\Organization::class, \App\Models\Bank::class, ])) { abort(403); } $this->validate(); // Resolve or create a standalone Location record $locationId = null; if ($this->country || $this->city) { $attributes = array_filter([ 'country_id' => $this->country ?: null, 'division_id' => $this->division ?: null, 'city_id' => $this->city ?: null, 'district_id' => $this->district ?: null, ]); $location = Location::whereNull('locatable_id') ->whereNull('locatable_type') ->where($attributes) ->first(); if (!$location) { $location = new Location($attributes); $location->save(); } $locationId = $location->id; } $call = Call::create([ 'callable_id' => session('activeProfileId'), 'callable_type' => $activeProfileType, 'tag_id' => $this->tagId ?: null, 'location_id' => $locationId, 'from' => now()->utc(), 'till' => $this->till ?: null, 'is_public' => $this->isPublic, ]); CallTranslation::create([ 'call_id' => $call->id, 'locale' => App::getLocale(), 'content' => $this->content ?: null, ]); $call->searchable(); $this->showModal = false; $this->reset(['content', 'till', 'tagId', 'country', 'division', 'city', 'district', 'isPublic']); $this->dispatch('callSaved'); $this->notification()->success( title: __('Saved'), description: __('Your Call has been published.') ); } public function render() { $activeProfileType = session('activeProfileType'); $activeProfileId = session('activeProfileId'); $profileName = $activeProfileType ? ($activeProfileType::find($activeProfileId)?->name ?? '') : ''; $canCreate = $activeProfileType && $activeProfileId ? CallCreditService::profileHasCredits($activeProfileType, (int) $activeProfileId) : false; // Calculate total spendable balance across all accounts for the active profile $spendableBalance = null; if ($activeProfileType && $activeProfileId) { $profile = $activeProfileType::find($activeProfileId); if ($profile) { $total = 0; foreach ($profile->accounts()->notRemoved()->get() as $account) { $balance = Transaction::where('from_account_id', $account->id) ->orWhere('to_account_id', $account->id) ->selectRaw('SUM(CASE WHEN to_account_id = ? THEN amount ELSE -amount END) as balance', [$account->id]) ->value('balance') ?? 0; $total += $balance - $account->limit_min; } $spendableBalance = $total; } } return view('livewire.calls.create', [ 'profileName' => $profileName, 'canCreate' => $canCreate, 'spendableBalance' => $spendableBalance, ]); } }