*/ protected $fillable = [ 'email', 'username', ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'last_connection' => 'datetime', 'email_verified_at' => 'datetime', ]; /** * Perform any actions required after the model boots. * * @return void */ protected static function booted() { // * EVENT CLOSURES. self::creating(function (self $model) { self::assertEmailIsUnique($model->getKey(), $model->email); }); self::updating(function (self $model) { self::assertEmailIsUnique($model->getKey(), $model->email); }); self::deleted(function (self $model) { if ($model->forceDeleting) { if ($model->existsInCrm()) { $model->api_data->delete(); } $model->notify(new DestroyedCustomer()); } else { $model->notify(new DestroyCustomer()); } }); self::restored(function () { \session()->flash( 'warning', \trans('Votre compte à été réactivé.') ); }); } // * METHODS /** * Asserts using validation that the email is unique. * * @param mixed $id * @param mixed $email * @return void * @throws \Illuminate\Validation\ValidationException If user email as already used. */ private static function assertEmailIsUnique(mixed $id, mixed $email): void { self::assertFieldIsUnique($id, 'email', $email); } /** * Asserts that the field is unique. * * @param mixed $id * @param string $field * @param mixed $value * @return void * @throws \Illuminate\Validation\ValidationException If the field is not unique. */ private static function assertFieldIsUnique(mixed $id, string $field, mixed $value): void { Validator::make([$field => $value], [ $field => Rule::unique('customers', $field)->ignore($id), ], [ $field . '.unique' => trans('La valeur :value pour le champ :attribute est déjà utilisée', [ 'attribute' => $field, 'value' => $value ]) ])->validate(); } /** * Get conversations with messages sorted by time. * * @return array */ public function conversations(): array { $dateSort = fn(Message $a, Message $b) => $a->created_at->timestamp > $b->created_at->timestamp; $messageFilter = fn(Collection $item) => $item->all(); $messageWith = ['fromCustomer', 'toCustomer']; /** * Hack workaround https://github.com/livewire/livewire/issues/1054 */ $sentM = $this->sentMessages() ->where('hide_from', false) ->with($messageWith)->get() ->groupBy('toCustomer.username') ->map($messageFilter)->toBase(); $receivedM = $this->messages() ->where('hide_to', false)->with($messageWith)->get() ->groupBy('fromCustomer.username')->map($messageFilter)->toBase(); $output = $receivedM->mapWithKeys( function (array $messagesArray, string $fromUsername) use (&$sentM, $dateSort) { $responses = collect(\array_merge( $sentM->pull($fromUsername) ?? [], $messagesArray )) // @phpstan-ignore-next-line ->sort($dateSort); $last = $responses->last(); $fromCustomer = collect($messagesArray)->first()->fromCustomer; return [$fromUsername => collect([ 'to' => $fromUsername, 'to_id' => ($fromCustomer) ? $fromCustomer->id : null, 'id' => $last->id, 'date' => $last->created_at, 'dateRaw' => $last->getRawOriginal('created_at'), 'preview' => Str::limit($last->message, 15), 'messages' => $responses->values()->all() ])]; } )->toBase(); // * Loop on messages sent (who haven't a response) $outputSentM = $sentM->mapWithKeys(function ($messagesArray) { $sent = collect($messagesArray); $last = $sent->last(); $toCustomer = $last->toCustomer; $toUserName = $toCustomer->username; return [$toUserName => collect([ 'to' => $toUserName, 'to_id' => $toCustomer->id, 'id' => $last->id, 'date' => $last->created_at, 'dateRaw' => $last->getRawOriginal('created_at'), 'preview' => Str::limit($last->message, 15), 'messages' => $sent->values()->all() ])]; })->toBase(); // @phpstan-ignore-next-line return $output->merge($outputSentM)->sort(function ($a, $b) { return Carbon::parse( Arr::get($a, 'dateRaw') )->timestamp < Carbon::parse( Arr::get($b, 'dateRaw') )->timestamp; })->toArray(); } /** * Get all notifications from user id. * * @return \Illuminate\Database\Eloquent\Collection */ public function getAllNotifications(): \Illuminate\Database\Eloquent\Collection { return Notification::query() ->whereHas('customer', fn(Builder $q) => $q->where('id', $this->getKey())) ->orWhere('customer_id', null) ->with('notifiable') ->orderBy('created_at', 'DESC') ->get(); } /** * Send the email verification notification. * * @return void */ public function sendEmailVerificationNotification(): void { $this->deleteIfDoesNotExistsInCrm(); $this->notify(new VerifyEmail()); } /** * Send the password reset notification. * * @param mixed $token * @return void */ public function sendPasswordResetNotification(mixed $token): void { $this->deleteIfDoesNotExistsInCrm(); /** @var string $token */ $this->notify(new ResetPassword($token)); } /** * Get all user Departaimant collection or create them if they dont exists. * * @return \Illuminate\Support\Collection */ public function getOrCreateAllDepartaimentsCollections(): \Illuminate\Support\Collection { /** @var \Illuminate\Database\Eloquent\Collection $collections */ $collections = $this->departaimantsCollections->keyBy(function ($item) { return $item->type->value(); }); /** @var \App\Enums\Departaimants\DepartaimantCollectionType $depColectionType */ foreach (DepartaimantCollectionType::toArray() as $depColectionType) { if (!$collections->has($depColectionType->value)) { $depCollection = new DepartaimantsCollection([ 'type' => $depColectionType->value ]); $depCollection->customer()->associate($this); $depCollection->save(); $collections->add($depCollection); } } return $collections; } /** * Check if the customer exists in CRM. * * @return boolean */ public function existsInCrm(): bool { return $this->hashcode and CrmCustomer::find($this->hashcode); } /** * Force delete and logout if is logged in case the customer * does not exists in CRM. * * @return void * @throws \Illuminate\Validation\ValidationException If the account is deleted because does not exists in CRM. */ private function deleteIfDoesNotExistsInCrm(): void { if ($this->existsInCrm()) { return; } /** @var static|null $loggedCustomer */ $loggedCustomer = Auth::guard('club')->user(); if (\optional($loggedCustomer)->id === $this->getKey()) { Auth::guard('club')->logout(); } $this->forceDelete(); throw ValidationException::withMessages([ 'error' => trans('Le compte n\'existe pas.') ]); } /** * Get api_data attribute, the crm Data of a customer. * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ public function apiData(): \Illuminate\Database\Eloquent\Casts\Attribute { return Attribute::make( get: fn(): CrmCustomer => self::cacheCrm($this->hashcode, "apiData", function () { /** @var \LDC\LDCCRM\CrmApiV4 */ $crm = app('crmAPI'); $crmCustomer = $crm->getCustomer($this->hashcode); if (!$crmCustomer) { throw new CrmException('CRM get Customer returned null'); } return $crmCustomer; }) ); } /** * Set the crm Data of a customer * * @param array $data * @return boolean * * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response. * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs. * @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber */ public function updateApiData(array $data): bool { // phpcs:enable /** @var \LDC\LDCCRM\Models\Customer|null */ $crmCustomer = $this->api_data; if (!$crmCustomer) { throw new CrmException('CRM get Customer returned null'); } if (isset($data["postal_code"])) { $data['postcode'] = Arr::get($data, 'postal_code'); } unset( $data["username"], $data["question_id"], $data["answers"], $data["postal_code"] ); $crmCustomer->fill($data); $result = $crmCustomer->save(); self::clearCrmCached($crmCustomer->hashcode, 'apiData'); return $result; } // * CRM CACHE /** * Clear cached apiData. * * @return void */ public function clearApiDataCache(): void { /** @var \LDC\LDCCRM\Models\Customer */ $crmCustomer = $this->api_data; self::clearCrmCached($crmCustomer->hashcode, 'apiData'); } /** * Put customer crm part in cache. * * @param string $uuid The customer uuid. * @param string $part_name The part name for the cache path. * @param \Closure $part The part to cache. * @param integer $ttl Time to live in seconds for the cache part. * @return mixed */ public static function cacheCrm(string $uuid, string $part_name, \Closure $part, int $ttl = 60 * 60): mixed { return Cache::remember("customers.{$uuid}.{$part_name}", $ttl, $part); } /** * Clear customer crm cache part or totally. * * @param string $uuid * @param string $part_name * @return void */ public static function clearCrmCached(string $uuid, string $part_name): void { $cache = "customers.{$uuid}"; if ($part_name) { $cache .= ".{$part_name}"; } Cache::forget($cache); } /** * This mutate Customer attributes to array * including only non critical properties and crm values * (One that can be send to front office without security breach). * * @return array */ public function toArray(): array { if ($this->hashcode) { return collect(array_merge( parent::toArray(), // @phpstan-ignore-next-line (array) $this->apiData ))->intersectByKeys([ 'email' => '', 'firstname' => '', 'lastname' => '', 'address' => '', 'address2' => '', 'postcode' => '', 'birth_date' => '', 'phone' => '', 'city' => '', 'country' => '', 'opt_in' => '', ])->toArray(); } else { return parent::toArray(); } //end if } // * RELATIONSHIPS /** * Get all of the departaimant collections for the Customer. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function departaimantsCollections(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(DepartaimantsCollection::class, 'customer_id'); } /** * The notifications that belong to the Customer. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function notifications(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Notification::class); } /** * The read notifications that belong to the Customer. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function readNotifications(): \Illuminate\Database\Eloquent\Relations\BelongsToMany { return $this->belongsToMany(Notification::class, 'notifications_read', 'customer_id', 'notification_id'); } /** * Get all of the messages for the Customer. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function messages(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Message::class, 'to'); } /** * Get all of the sentMessages for the Customer. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function sentMessages(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Message::class, 'from'); } /** * Get all of the sentMessages for the Customer. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function ratings(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Rating::class); } /** * Get all of the recipes that are favorite by this customer. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function recipes(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(Recipe::class, 'customerable')->with('products', 'consumptionMoment')->withTimestamps(); } /** * Get all of the products that are favorite by this customer. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function products(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(Product::class, 'customerable')->withTimestamps(); } /** * Get all of the products that are favorite by this customer. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function surveyAnswers(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(SurveysAnswer::class, 'customerable')->with('newsClub')->withTimestamps(); } }