feat: complete Epic 0 — foundation migration & infrastructure setup

Stories 0.2-0.5: rename folders→declarations (backend+frontend), configure
Redis for cache/queue/sessions, add foundation database migrations
(permissions, archived_at), replace DeclarationStatus enum with architecture
lifecycle values, create DeclarationObserver for status transition validation
and auto-archive, fix controller status transitions to respect observer rules.

93 tests pass (240 assertions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 18:25:32 +00:00
parent d380df4074
commit fd43a6f429
105 changed files with 3899 additions and 1558 deletions

View File

@@ -3,10 +3,10 @@
namespace App\Http\Controllers\Client;
use App\Enums\ActorType;
use App\Enums\FolderStatus;
use App\Enums\DeclarationStatus;
use App\Enums\MessageType;
use App\Http\Controllers\Controller;
use App\Mail\FolderTextMessageMail;
use App\Mail\DeclarationTextMessageMail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
@@ -20,11 +20,11 @@ class UploadController extends Controller
*/
public function show(Request $request, string $token): Response
{
$invitation = $request->attributes->get('folder_invitation');
$folder = $invitation->folder;
$invitation = $request->attributes->get('declaration_invitation');
$declaration = $invitation->declaration;
$folder->load(['client']);
$documents = $folder->getMedia('documents')->map(fn ($m) => [
$declaration->load(['client']);
$documents = $declaration->getMedia('documents')->map(fn ($m) => [
'id' => $m->id,
'name' => $m->name,
'file_name' => $m->file_name,
@@ -33,10 +33,10 @@ class UploadController extends Controller
])->values()->all();
return Inertia::render('client/Upload', [
'folder' => [
'id' => $folder->id,
'title' => $folder->title,
'client_name' => $folder->client->company_name,
'declaration' => [
'id' => $declaration->id,
'title' => $declaration->title,
'client_name' => $declaration->client->company_name,
],
'token' => $token,
'documents' => $documents,
@@ -50,38 +50,42 @@ class UploadController extends Controller
*/
public function store(Request $request, string $token): RedirectResponse
{
$invitation = $request->attributes->get('folder_invitation');
$folder = $invitation->folder;
$invitation = $request->attributes->get('declaration_invitation');
$declaration = $invitation->declaration;
$request->validate([
'files' => ['required', 'array', 'min:1'],
'files.*' => ['file', 'max:10240'],
]);
$message = $folder->messages()->create([
$message = $declaration->messages()->create([
'type' => MessageType::Text,
'body' => 'Documents déposés par le client.',
'sent_by_type' => ActorType::Client,
'sent_by_id' => $folder->client_id,
'sent_by_id' => $declaration->client_id,
'metadata' => ['invitation_id' => $invitation->id],
]);
foreach ($request->file('files') as $file) {
$folder->addMedia($file)
$declaration->addMedia($file)
->withCustomProperties([
'message_id' => $message->id,
'uploaded_by_type' => ActorType::Client,
'uploaded_by_id' => $folder->client_id,
'uploaded_by_id' => $declaration->client_id,
])
->toMediaCollection('documents');
}
$folder->update(['status' => FolderStatus::DocumentsReceived]);
// Only transition to en_cours if the current status allows it
$allowed = DeclarationStatus::allowedTransitions()[$declaration->status->value] ?? [];
if (in_array(DeclarationStatus::EnCours, $allowed)) {
$declaration->update(['status' => DeclarationStatus::EnCours]);
}
$recipient = $folder->assignee ?? $folder->creator;
$recipient = $declaration->assignee ?? $declaration->creator;
if ($recipient?->email) {
Mail::to($recipient->email)->send(
new FolderTextMessageMail($folder, 'Le client a déposé des documents.', null)
new DeclarationTextMessageMail($declaration, 'Le client a déposé des documents.', null)
);
}