ldc-legaulois/app/Models/Customer.php

482 lines
16 KiB
PHP
Raw Permalink Normal View History

2024-07-01 10:22:38 +02:00
<?php
namespace App\Models;
2024-07-11 15:46:43 +02:00
use App\Enums\Departaimants\DepartaimantCollectionType;
2024-07-05 16:34:57 +02:00
use App\Notifications\Customer\DestroyCustomer;
use App\Notifications\Customer\DestroyedCustomer;
use App\Notifications\Customer\ResetPassword;
2024-07-05 16:34:57 +02:00
use App\Notifications\Customer\VerifyEmail;
2024-07-30 16:05:27 +02:00
use App\Traits\Models\ActivityLog;
2024-07-05 16:34:57 +02:00
use Illuminate\Contracts\Auth\MustVerifyEmail;
2024-07-30 16:05:27 +02:00
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
2024-07-01 10:22:38 +02:00
use Illuminate\Database\Eloquent\Factories\HasFactory;
2026-09-25 16:25:31 +02:00
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
2024-07-05 16:34:57 +02:00
use Illuminate\Database\Eloquent\SoftDeletes;
2024-07-01 10:50:41 +02:00
use Illuminate\Foundation\Auth\User as Authenticatable;
2024-07-05 16:34:57 +02:00
use Illuminate\Notifications\Notifiable;
2024-07-16 10:47:23 +02:00
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
2024-07-11 15:46:43 +02:00
use Illuminate\Support\Collection;
2024-07-30 16:05:27 +02:00
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
2024-07-05 16:34:57 +02:00
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
2024-07-05 16:34:57 +02:00
use Illuminate\Validation\Rule;
2024-07-30 16:05:27 +02:00
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
2024-07-01 10:22:38 +02:00
2024-07-11 15:46:43 +02:00
/**
2026-09-25 16:25:31 +02:00
* @property string $hashcode Customer unique CRM hashcode.
* @property string $email Customer unique email.
* @property string $username Customer username.
* @property string $remember_token Customer remember token.
2024-07-30 16:05:27 +02:00
* @property \LDC\LDCCRM\Models\Customer|null $api_data
2026-09-25 16:25:31 +02:00
* @property \Carbon\Carbon|null $email_verified_at
* @property \Carbon\Carbon|null $last_connection Date de derniere connexion
2024-07-30 16:05:27 +02:00
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
2024-07-11 15:46:43 +02:00
*/
2024-07-05 16:34:57 +02:00
class Customer extends Authenticatable implements MustVerifyEmail
2024-07-01 10:22:38 +02:00
{
2024-07-30 16:05:27 +02:00
use ActivityLog;
2024-07-01 10:22:38 +02:00
use HasFactory;
2024-07-05 16:34:57 +02:00
use Notifiable;
use SoftDeletes;
2024-07-30 16:05:27 +02:00
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'email',
'username',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'last_connection' => 'datetime',
'email_verified_at' => 'datetime',
];
2024-07-05 16:34:57 +02:00
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
// * EVENT CLOSURES.
self::creating(function (self $model) {
2024-10-21 16:57:20 +02:00
self::assertEmailIsUnique($model->getKey(), $model->email);
2024-07-05 16:34:57 +02:00
});
self::updating(function (self $model) {
2024-10-21 16:57:20 +02:00
self::assertEmailIsUnique($model->getKey(), $model->email);
2024-07-05 16:34:57 +02:00
});
self::deleted(function (self $model) {
if ($model->forceDeleting) {
2024-07-30 16:05:27 +02:00
if ($model->existsInCrm()) {
$model->api_data->delete();
}
2026-10-19 11:16:32 +02:00
$model->notify(new DestroyedCustomer($model->api_data->firstname));
2024-07-05 16:34:57 +02:00
} else {
2026-10-19 11:16:32 +02:00
$model->notify(new DestroyCustomer($model->api_data->firstname));
2024-07-05 16:34:57 +02:00
}
});
self::restored(function () {
\session()->flash(
'warning',
\trans('Votre compte à été réactivé.')
);
});
}
2024-07-30 16:05:27 +02:00
// * METHODS
2024-07-15 10:23:12 +02:00
2024-07-16 10:47:23 +02:00
/**
* Asserts using validation that the email is unique.
2024-07-16 10:47:23 +02:00
*
2024-07-30 16:05:27 +02:00
* @throws \Illuminate\Validation\ValidationException If user email as already used.
2024-07-16 10:47:23 +02:00
*/
2024-07-30 16:05:27 +02:00
private static function assertEmailIsUnique(mixed $id, mixed $email): void
2024-07-16 10:47:23 +02:00
{
2024-07-30 16:05:27 +02:00
self::assertFieldIsUnique($id, 'email', $email);
2024-07-16 10:47:23 +02:00
}
/**
* Asserts that the field is unique.
2024-07-16 10:47:23 +02:00
*
2024-07-30 16:05:27 +02:00
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
2024-07-16 10:47:23 +02:00
*/
2024-07-30 16:05:27 +02:00
private static function assertFieldIsUnique(mixed $id, string $field, mixed $value): void
2024-07-16 10:47:23 +02:00
{
2024-07-30 16:05:27 +02:00
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', [
2024-07-30 16:05:27 +02:00
'attribute' => $field,
2026-09-25 16:25:31 +02:00
'value' => $value,
]),
2024-07-30 16:05:27 +02:00
])->validate();
2024-07-16 10:47:23 +02:00
}
/**
* Get conversations with messages sorted by time.
2024-07-16 10:47:23 +02:00
*/
public function conversations(): array
{
2026-09-25 16:25:31 +02:00
$dateSort = fn (Message $a, Message $b) => $a->created_at->timestamp > $b->created_at->timestamp;
$messageFilter = fn (Collection $item) => $item->all();
$messageWith = ['fromCustomer', 'toCustomer'];
2024-07-16 10:47:23 +02:00
/**
* 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();
2024-07-30 16:05:27 +02:00
$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;
2026-09-25 16:25:31 +02:00
2024-07-30 16:05:27 +02:00
return [$fromUsername => collect([
'to' => $fromUsername,
'to_id' => ($fromCustomer) ? $fromCustomer->id : null,
2024-07-30 16:05:27 +02:00
'id' => $last->id,
'date' => $last->created_at,
'dateRaw' => $last->getRawOriginal('created_at'),
'preview' => Str::limit($last->message, 15),
2026-09-25 16:25:31 +02:00
'messages' => $responses->values()->all(),
2024-07-30 16:05:27 +02:00
])];
}
)->toBase();
// * Loop on messages sent (who haven't a response)
2024-07-16 10:47:23 +02:00
$outputSentM = $sentM->mapWithKeys(function ($messagesArray) {
$sent = collect($messagesArray);
$last = $sent->last();
$toCustomer = $last->toCustomer;
$toUserName = $toCustomer->username;
2026-09-25 16:25:31 +02:00
2024-07-16 10:47:23 +02:00
return [$toUserName => collect([
2024-07-30 16:05:27 +02:00
'to' => $toUserName,
'to_id' => $toCustomer->id,
'id' => $last->id,
'date' => $last->created_at,
'dateRaw' => $last->getRawOriginal('created_at'),
'preview' => Str::limit($last->message, 15),
2026-09-25 16:25:31 +02:00
'messages' => $sent->values()->all(),
2024-07-16 10:47:23 +02:00
])];
})->toBase();
2024-07-30 16:05:27 +02:00
// @phpstan-ignore-next-line
2024-07-16 10:47:23 +02:00
return $output->merge($outputSentM)->sort(function ($a, $b) {
return Carbon::parse(
Arr::get($a, 'dateRaw')
2024-07-30 16:05:27 +02:00
)->timestamp < Carbon::parse(
Arr::get($b, 'dateRaw')
)->timestamp;
2024-07-16 10:47:23 +02:00
})->toArray();
}
2024-07-05 16:34:57 +02:00
/**
2024-07-30 16:05:27 +02:00
* Get all notifications from user id.
2024-07-05 16:34:57 +02:00
*/
2024-07-30 16:05:27 +02:00
public function getAllNotifications(): \Illuminate\Database\Eloquent\Collection
2024-07-05 16:34:57 +02:00
{
2024-07-30 16:05:27 +02:00
return Notification::query()
2026-09-25 16:25:31 +02:00
->whereHas('customer', fn (Builder $q) => $q->where('id', $this->getKey()))
2024-07-30 16:05:27 +02:00
->orWhere('customer_id', null)
->with('notifiable')
->orderBy('created_at', 'DESC')
->get();
2024-07-05 16:34:57 +02:00
}
/**
2024-07-30 16:05:27 +02:00
* Send the email verification notification.
2024-07-05 16:34:57 +02:00
*/
public function sendEmailVerificationNotification(): void
2024-07-05 16:34:57 +02:00
{
2024-07-30 16:05:27 +02:00
$this->deleteIfDoesNotExistsInCrm();
2026-10-19 11:16:32 +02:00
$this->notify(new VerifyEmail($this->api_data->firstname));
2024-07-05 16:34:57 +02:00
}
/**
* Send the password reset notification.
*/
public function sendPasswordResetNotification(mixed $token): void
{
$this->deleteIfDoesNotExistsInCrm();
/** @var string $token */
2026-10-19 11:16:32 +02:00
$this->notify(new ResetPassword($token, $this->api_data->firstname));
}
2024-07-15 10:23:12 +02:00
/**
2024-07-30 16:05:27 +02:00
* Get all user Departaimant collection or create them if they dont exists.
2024-07-15 10:23:12 +02:00
*/
2026-09-25 16:25:31 +02:00
public function getOrCreateAllDepartaimentsCollections(): Collection
2024-07-15 10:23:12 +02:00
{
2024-07-30 16:05:27 +02:00
/** @var \Illuminate\Database\Eloquent\Collection $collections */
$collections = $this->departaimantsCollections->keyBy(function ($item) {
return $item->type->value();
});
2024-07-30 16:05:27 +02:00
/** @var \App\Enums\Departaimants\DepartaimantCollectionType $depColectionType */
foreach (DepartaimantCollectionType::toArray() as $depColectionType) {
2026-09-25 16:25:31 +02:00
if (! $collections->has($depColectionType->value)) {
2024-07-15 10:23:12 +02:00
$depCollection = new DepartaimantsCollection([
2026-09-25 16:25:31 +02:00
'type' => $depColectionType->value,
2024-07-15 10:23:12 +02:00
]);
$depCollection->customer()->associate($this);
$depCollection->save();
$collections->add($depCollection);
}
}
2026-09-25 16:25:31 +02:00
2024-07-15 10:23:12 +02:00
return $collections;
}
/**
2024-07-30 16:05:27 +02:00
* Check if the customer exists in CRM.
2024-07-15 10:23:12 +02:00
*/
2024-07-30 16:05:27 +02:00
public function existsInCrm(): bool
2024-07-15 10:23:12 +02:00
{
2024-07-30 16:05:27 +02:00
return $this->hashcode and CrmCustomer::find($this->hashcode);
}
/**
* Force delete and logout if is logged in case the customer
* does not exists in CRM.
*
* @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();
2024-10-21 16:57:20 +02:00
if (\optional($loggedCustomer)->id === $this->getKey()) {
2024-07-30 16:05:27 +02:00
Auth::guard('club')->logout();
}
$this->forceDelete();
throw ValidationException::withMessages([
2026-09-25 16:25:31 +02:00
'error' => trans('Le compte n\'existe pas.'),
2024-07-30 16:05:27 +02:00
]);
2024-07-15 10:23:12 +02:00
}
2024-07-30 16:05:27 +02:00
/**
* Get api_data attribute, the crm Data of a customer.
2024-07-30 16:05:27 +02:00
*/
2026-09-25 16:25:31 +02:00
public function apiData(): Attribute
2024-07-30 16:05:27 +02:00
{
return Attribute::make(
2026-09-25 16:25:31 +02:00
get: fn (): CrmCustomer => self::cacheCrm($this->hashcode, 'apiData', function () {
2024-07-30 16:05:27 +02:00
/** @var \LDC\LDCCRM\CrmApiV4 */
$crm = app('crmAPI');
$crmCustomer = $crm->getCustomer($this->hashcode);
2026-09-25 16:25:31 +02:00
if (! $crmCustomer) {
2024-07-30 16:05:27 +02:00
throw new CrmException('CRM get Customer returned null');
}
2026-09-25 16:25:31 +02:00
2024-07-30 16:05:27 +02:00
return $crmCustomer;
})
);
}
2024-07-05 16:34:57 +02:00
/**
2024-07-30 16:05:27 +02:00
* Set the crm Data of a customer
*
*
* @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.
2026-09-25 16:25:31 +02:00
*
2024-07-30 16:05:27 +02:00
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function updateApiData(array $data): bool
{
// phpcs:enable
/** @var \LDC\LDCCRM\Models\Customer|null */
$crmCustomer = $this->api_data;
2026-09-25 16:25:31 +02:00
if (! $crmCustomer) {
2024-07-30 16:05:27 +02:00
throw new CrmException('CRM get Customer returned null');
}
2026-09-25 16:25:31 +02:00
if (isset($data['postal_code'])) {
$data['postcode'] = Arr::get($data, 'postal_code');
}
unset(
2026-09-25 16:25:31 +02:00
$data['username'],
$data['question_id'],
$data['answers'],
$data['postal_code']
);
2024-07-30 16:05:27 +02:00
$crmCustomer->fill($data);
$result = $crmCustomer->save();
self::clearCrmCached($crmCustomer->hashcode, 'apiData');
return $result;
}
// * CRM CACHE
/**
* Clear cached apiData.
2024-07-05 16:34:57 +02:00
*/
2024-07-30 16:05:27 +02:00
public function clearApiDataCache(): void
2024-07-05 16:34:57 +02:00
{
2024-07-30 16:05:27 +02:00
/** @var \LDC\LDCCRM\Models\Customer */
$crmCustomer = $this->api_data;
self::clearCrmCached($crmCustomer->hashcode, 'apiData');
2024-07-05 16:34:57 +02:00
}
/**
* Put customer crm part in cache.
2024-07-05 16:34:57 +02:00
*
2026-09-25 16:25:31 +02:00
* @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 int $ttl Time to live in seconds for the cache part.
2024-07-30 16:05:27 +02:00
*/
public static function cacheCrm(string $uuid, string $part_name, \Closure $part, int $ttl = 60 * 60): mixed
2024-07-30 16:05:27 +02:00
{
return Cache::remember("customers.{$uuid}.{$part_name}", $ttl, $part);
}
/**
* Clear customer crm cache part or totally.
2024-07-05 16:34:57 +02:00
*/
2024-07-30 16:05:27 +02:00
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).
2024-07-30 16:05:27 +02:00
*/
public function toArray(): array
2024-07-30 16:05:27 +02:00
{
if ($this->hashcode) {
return collect(array_merge(
parent::toArray(),
// @phpstan-ignore-next-line
(array) $this->apiData
2024-07-30 16:05:27 +02:00
))->intersectByKeys([
'email' => '',
'firstname' => '',
'lastname' => '',
'address' => '',
'address2' => '',
'postcode' => '',
'birth_date' => '',
'phone' => '',
'city' => '',
'country' => '',
'opt_in' => '',
])->toArray();
2026-09-25 16:25:31 +02:00
}
return parent::toArray();
// end if
2024-07-30 16:05:27 +02:00
}
// * RELATIONSHIPS
/**
* Get all of the departaimant collections for the Customer.
2024-07-30 16:05:27 +02:00
*/
2026-09-25 16:25:31 +02:00
public function departaimantsCollections(): HasMany
2024-07-30 16:05:27 +02:00
{
return $this->hasMany(DepartaimantsCollection::class, 'customer_id');
}
/**
* The notifications that belong to the Customer.
2024-07-30 16:05:27 +02:00
*/
2026-09-25 16:25:31 +02:00
public function notifications(): HasMany
2024-07-05 16:34:57 +02:00
{
return $this->hasMany(Notification::class);
2024-07-30 16:05:27 +02:00
}
/**
* The read notifications that belong to the Customer.
2024-07-30 16:05:27 +02:00
*/
2026-09-25 16:25:31 +02:00
public function readNotifications(): BelongsToMany
2024-07-30 16:05:27 +02:00
{
return $this->belongsToMany(Notification::class, 'notifications_read', 'customer_id', 'notification_id');
}
/**
* Get all of the messages for the Customer.
2024-07-30 16:05:27 +02:00
*/
2026-09-25 16:25:31 +02:00
public function messages(): HasMany
2024-07-30 16:05:27 +02:00
{
return $this->hasMany(Message::class, 'to');
}
/**
* Get all of the sentMessages for the Customer.
2024-07-30 16:05:27 +02:00
*/
2026-09-25 16:25:31 +02:00
public function sentMessages(): HasMany
2024-07-30 16:05:27 +02:00
{
return $this->hasMany(Message::class, 'from');
2024-07-05 16:34:57 +02:00
}
/**
* Get all of the sentMessages for the Customer.
*/
2026-09-25 16:25:31 +02:00
public function ratings(): HasMany
{
return $this->hasMany(Rating::class);
}
/**
* Get all of the recipes that are favorite by this customer.
*/
2026-09-25 16:25:31 +02:00
public function recipes(): MorphToMany
{
return $this->morphedByMany(Recipe::class, 'customerable')->with('products', 'consumptionMoments')->withTimestamps();
}
/**
* Get all of the products that are favorite by this customer.
*/
2026-09-25 16:25:31 +02:00
public function products(): MorphToMany
{
return $this->morphedByMany(Product::class, 'customerable')->withTimestamps();
}
/**
* Get all of the products that are favorite by this customer.
*/
2026-09-25 16:25:31 +02:00
public function surveyAnswers(): MorphToMany
{
return $this->morphedByMany(SurveysAnswer::class, 'customerable')->with('newsClub')->withTimestamps();
}
2024-07-01 10:22:38 +02:00
}