2026-03-12 18:25:32 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Observers;
|
|
|
|
|
|
|
|
|
|
use App\Enums\DeclarationStatus;
|
2026-03-26 14:31:36 +01:00
|
|
|
use App\Mail\DeclarationFileRequestMail;
|
2026-03-12 18:25:32 +00:00
|
|
|
use App\Models\Declaration;
|
2026-03-26 14:31:36 +01:00
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Support\Facades\Mail;
|
2026-03-12 18:25:32 +00:00
|
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
|
|
|
|
|
|
class DeclarationObserver
|
|
|
|
|
{
|
2026-03-26 14:31:36 +01:00
|
|
|
/**
|
|
|
|
|
* When true, the observer skips sending the client email on en_attente_client.
|
|
|
|
|
* Set this before updating status when the caller already sends the email.
|
|
|
|
|
*/
|
|
|
|
|
public static bool $suppressEmail = false;
|
|
|
|
|
|
2026-03-12 18:25:32 +00:00
|
|
|
/**
|
|
|
|
|
* Handle the Declaration "updating" event.
|
|
|
|
|
*
|
|
|
|
|
* Validates status transitions and auto-archives when status becomes "ferme".
|
|
|
|
|
*/
|
|
|
|
|
public function updating(Declaration $declaration): void
|
|
|
|
|
{
|
|
|
|
|
if (! $declaration->isDirty('status')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$oldStatus = $declaration->getOriginal('status');
|
|
|
|
|
$newStatus = $declaration->status;
|
|
|
|
|
|
|
|
|
|
// Handle both string and enum values
|
|
|
|
|
$oldValue = $oldStatus instanceof DeclarationStatus ? $oldStatus->value : (string) $oldStatus;
|
|
|
|
|
$newValue = $newStatus instanceof DeclarationStatus ? $newStatus->value : (string) $newStatus;
|
|
|
|
|
|
|
|
|
|
$allowed = DeclarationStatus::allowedTransitions()[$oldValue] ?? [];
|
|
|
|
|
|
|
|
|
|
if (! in_array($newValue, $allowed)) {
|
|
|
|
|
throw ValidationException::withMessages([
|
|
|
|
|
'status' => "Invalid status transition from '{$oldValue}' to '{$newValue}'.",
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-archive when status becomes "ferme"
|
|
|
|
|
if ($newValue === DeclarationStatus::Ferme) {
|
|
|
|
|
$declaration->archived_at = now();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-26 14:31:36 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle the Declaration "updated" event.
|
|
|
|
|
*
|
|
|
|
|
* Dispatches side effects on status transitions:
|
|
|
|
|
* - en_attente_client: queues client email via DeclarationFileRequestMail
|
|
|
|
|
* - ferme: invalidates dashboard cache for all workspace users
|
|
|
|
|
*/
|
|
|
|
|
public function updated(Declaration $declaration): void
|
|
|
|
|
{
|
|
|
|
|
if (! $declaration->wasChanged('status')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$newStatus = $declaration->status instanceof DeclarationStatus
|
|
|
|
|
? $declaration->status->value
|
|
|
|
|
: (string) $declaration->status;
|
|
|
|
|
|
|
|
|
|
if ($newStatus === DeclarationStatus::EnAttenteClient && ! static::$suppressEmail) {
|
|
|
|
|
$this->sendClientFileRequestEmail($declaration);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset suppression flag after each update
|
|
|
|
|
static::$suppressEmail = false;
|
|
|
|
|
|
|
|
|
|
if ($newStatus === DeclarationStatus::Ferme) {
|
|
|
|
|
$this->invalidateDashboardCache($declaration);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function sendClientFileRequestEmail(Declaration $declaration): void
|
|
|
|
|
{
|
|
|
|
|
$declaration->loadMissing('client');
|
|
|
|
|
$client = $declaration->client;
|
|
|
|
|
|
|
|
|
|
if (! $client || ! $client->contact_email) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$mailJob = DB::transaction(function () use ($declaration, $client) {
|
|
|
|
|
$invitation = $declaration->invitations()
|
|
|
|
|
->whereNull('used_at')
|
|
|
|
|
->latest()
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if ($invitation && $invitation->isValid()) {
|
|
|
|
|
// Reuse valid unused invitation, but sync email with current client
|
|
|
|
|
if ($invitation->email !== $client->contact_email) {
|
|
|
|
|
$invitation->update(['email' => $client->contact_email]);
|
|
|
|
|
$invitation->refresh();
|
|
|
|
|
}
|
|
|
|
|
} elseif ($invitation && ! $invitation->isValid()) {
|
|
|
|
|
// Expired but unused — renew it
|
|
|
|
|
$invitation->update([
|
|
|
|
|
'email' => $client->contact_email,
|
|
|
|
|
'expires_at' => now()->addDays(30),
|
|
|
|
|
]);
|
|
|
|
|
$invitation->refresh();
|
|
|
|
|
} else {
|
|
|
|
|
// No unused invitation exists (all used or none) — create fresh
|
|
|
|
|
$invitation = $declaration->invitations()->create([
|
|
|
|
|
'email' => $client->contact_email,
|
|
|
|
|
'expires_at' => now()->addDays(30),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$body = 'Nous vous invitons à déposer les documents complémentaires pour votre déclaration "'
|
|
|
|
|
. $declaration->title . '".';
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'email' => $client->contact_email,
|
|
|
|
|
'mailable' => new DeclarationFileRequestMail($declaration, $invitation, $body),
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Mail::to($mailJob['email'])->queue($mailJob['mailable']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function invalidateDashboardCache(Declaration $declaration): void
|
|
|
|
|
{
|
|
|
|
|
$declaration->loadMissing('workspace.users');
|
|
|
|
|
$workspace = $declaration->workspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$workspace->users->each(function ($user) use ($workspace) {
|
|
|
|
|
Cache::forget("dashboard:{$workspace->id}:{$user->id}");
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-03-12 18:25:32 +00:00
|
|
|
}
|