Files
L-Ami-Fiduciaire/app/Http/Controllers/FolderController.php
Saad Zoubir 7a18c40361 chore: add BMAD framework modules, folder features, and tooling configs
Includes BMAD bmb/bmm/cis/tea workflow modules, folder (declaration)
feature implementation (controllers, models, enums, views, tests),
claude/cursor command configs, and email templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 21:24:17 +01:00

329 lines
13 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Enums\FolderPriority;
use App\Enums\FolderStatus;
use App\Enums\FolderType;
use App\Http\Requests\StoreFolderRequest;
use App\Http\Requests\UpdateFolderRequest;
use App\Models\Client;
use App\Models\Folder;
use App\Models\MediaDownload;
use App\Models\Workspace;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class FolderController extends Controller
{
protected function folderTypeLabels(): array
{
return [
FolderType::VAT => 'TVA',
FolderType::VatMonthly => 'TVA mensuelle',
FolderType::VatQuarterly => 'TVA trimestrielle',
FolderType::CorporateTax => 'IS',
FolderType::IncomeTax => 'IR',
FolderType::CNSS => 'CNSS',
FolderType::AnnualBalance => 'Bilan',
FolderType::Other => 'Autre',
];
}
protected function folderStatusLabels(): array
{
return [
FolderStatus::Draft => 'Brouillon',
FolderStatus::WaitingDocuments => 'En attente documents',
FolderStatus::DocumentsReceived => 'Documents reçus',
FolderStatus::Processing => 'En cours de traitement',
FolderStatus::AdditionalDocumentsRequested => 'Pièces complémentaires demandées',
FolderStatus::WaitingClientValidation => 'En attente validation client',
FolderStatus::Validated => 'Validé',
FolderStatus::Closed => 'Clôturé',
FolderStatus::Cancelled => 'Annulé',
];
}
protected function folderPriorityLabels(): array
{
return [
FolderPriority::Low => 'Basse',
FolderPriority::Medium => 'Normale',
FolderPriority::High => 'Haute',
];
}
protected function currentWorkspace(Request $request): Workspace
{
$workspaceId = $request->session()->get('current_workspace_id');
return Workspace::query()->findOrFail($workspaceId);
}
/**
* Display a listing of the folders.
*/
public function index(Request $request): Response
{
$workspace = $this->currentWorkspace($request);
$perPage = min(max((int) $request->input('per_page', 10), 10), 100);
$folders = $workspace->folders()
->with(['client', 'assignee'])
->latest()
->paginate($perPage)
->through(fn (Folder $folder) => [
'id' => $folder->id,
'title' => $folder->title,
'type' => $folder->type->value,
'client_name' => $folder->client->company_name,
'status' => $folder->status->value,
'due_date' => $folder->due_date?->format('Y-m-d'),
'showUrl' => route('folders.show', $folder),
'editUrl' => route('folders.edit', $folder),
'destroyUrl' => route('folders.destroy', $folder),
]);
return Inertia::render('folders/Index', [
'folders' => $folders,
'createUrl' => route('folders.create'),
'workspaceName' => $workspace->name,
]);
}
/**
* Show the form for creating a new folder.
*/
public function create(Request $request): Response
{
$workspace = $this->currentWorkspace($request);
$initialClientId = $request->integer('client_id', 0) ?: null;
return Inertia::render('folders/Create', [
'indexUrl' => route('folders.index'),
'storeUrl' => route('folders.store'),
'initialClientId' => $initialClientId,
'folderTypeLabels' => $this->folderTypeLabels(),
'folderStatusLabels' => $this->folderStatusLabels(),
'folderPriorityLabels' => $this->folderPriorityLabels(),
'clients' => $workspace->clients()->orderBy('company_name')->get(['id', 'company_name'])->map(fn (Client $c) => [
'id' => $c->id,
'company_name' => $c->company_name,
])->values()->all(),
'workspaceUsers' => $workspace->users()
->orderBy('users.name')
->select('users.id', 'users.name', 'users.email')
->get()
->map(fn ($u) => [
'id' => $u->id,
'name' => $u->name,
'email' => $u->email,
])->values()->all(),
]);
}
/**
* Store a newly created folder in storage.
*/
public function store(StoreFolderRequest $request): RedirectResponse
{
$workspace = $this->currentWorkspace($request);
$data = $request->validated();
$data['workspace_id'] = $workspace->id;
$data['created_by'] = $request->user()?->id;
$data['status'] = $data['status'] ?? FolderStatus::Draft->value;
Folder::query()->create($data);
return to_route('folders.index');
}
/**
* Display the specified folder.
*/
public function show(Request $request, Folder $folder): Response
{
$workspace = $this->currentWorkspace($request);
$this->authorizeFolder($workspace, $folder);
$folder->load(['client', 'creator', 'assignee', 'messages' => fn ($q) => $q->with(['senderUser', 'senderClient'])->latest()]);
$allMedia = $folder->getMedia('documents');
$downloadedMediaIds = MediaDownload::query()
->where('user_id', $request->user()->id)
->whereIn('media_id', $allMedia->pluck('id'))
->pluck('media_id')
->all();
$messages = $folder->messages->map(function ($m) use ($folder, $allMedia, $downloadedMediaIds) {
$attachments = $allMedia
->filter(fn ($media) => $media->getCustomProperty('message_id') === $m->id)
->map(fn ($media) => [
'id' => $media->id,
'file_name' => $media->file_name,
'mime_type' => $media->mime_type,
'size' => $media->human_readable_size,
'downloadUrl' => route('folders.media.download', ['folder' => $folder, 'mediaId' => $media->id]),
'is_downloaded' => in_array($media->id, $downloadedMediaIds),
])
->values()
->all();
$confirmationStatus = null;
if ($m->type->value === 'confirmation') {
$confirmationStatus = $folder->refused_at ? 'refused' : ($folder->validated_at ? 'confirmed' : 'pending');
}
return [
'id' => $m->id,
'type' => $m->type->value,
'body' => $m->body,
'sent_by_type' => $m->sent_by_type->value,
'sender_name' => $m->sender_name,
'created_at' => $m->created_at->format('Y-m-d H:i'),
'attachments' => $attachments,
'confirmation_status' => $confirmationStatus,
];
})->values()->all();
$documents = $allMedia->map(fn ($m) => [
'id' => $m->id,
'name' => $m->name,
'file_name' => $m->file_name,
'size' => $m->human_readable_size,
'created_at' => $m->created_at->format('d/m/Y H:i'),
'uploaded_by' => $m->getCustomProperty('uploaded_by_type') === 'user' ? 'Comptable' : 'Client',
'downloadUrl' => route('folders.media.download', ['folder' => $folder, 'mediaId' => $m->id]),
'is_downloaded' => in_array($m->id, $downloadedMediaIds),
])->values()->all();
return Inertia::render('folders/Show', [
'folder' => [
'id' => $folder->id,
'title' => $folder->title,
'type' => $folder->type->value,
'client_id' => $folder->client_id,
'client_name' => $folder->client->company_name,
'period_year' => $folder->period_year,
'period_month' => $folder->period_month,
'period_quarter' => $folder->period_quarter,
'due_date' => $folder->due_date?->format('Y-m-d'),
'status' => $folder->status->value,
'priority' => $folder->priority?->value,
'assigned_to' => $folder->assigned_to,
'assignee_name' => $folder->assignee?->name,
'validated_at' => $folder->validated_at?->format('Y-m-d H:i'),
'closed_at' => $folder->closed_at?->format('Y-m-d H:i'),
'notes_internal' => $folder->notes_internal,
'notes_client' => $folder->notes_client,
'created_at' => $folder->created_at?->format('Y-m-d H:i'),
],
'messages' => $messages,
'documents' => $documents,
'messagesStoreUrl' => route('folders.messages.store', $folder),
'mediaStoreUrl' => route('folders.media.store', $folder),
'messageTypeLabels' => [
'invite' => 'Invitation',
'situation' => 'Situation',
'file_request' => 'Demande de pièces',
'confirmation' => 'Demande de validation',
'text' => 'Message',
],
'indexUrl' => route('folders.index'),
'editUrl' => route('folders.edit', $folder),
'workspaceUsers' => $workspace->users()->orderBy('users.name')
->select('users.id', 'users.name', 'users.email')
->get()->map(fn ($u) => ['id' => $u->id, 'name' => $u->name])
->values()->all(),
'mentionStoreUrl' => route('folders.mentions.store', $folder),
'canMention' => in_array(
$workspace->users()->where('users.id', $request->user()->id)->first()?->pivot?->role?->value,
['owner', 'manager']
),
]);
}
/**
* Show the form for editing the specified folder.
*/
public function edit(Request $request, Folder $folder): Response
{
$workspace = $this->currentWorkspace($request);
$this->authorizeFolder($workspace, $folder);
return Inertia::render('folders/Edit', [
'folder' => [
'id' => $folder->id,
'title' => $folder->title,
'type' => $folder->type->value,
'client_id' => $folder->client_id,
'period_year' => $folder->period_year,
'period_month' => $folder->period_month,
'period_quarter' => $folder->period_quarter,
'due_date' => $folder->due_date?->format('Y-m-d'),
'status' => $folder->status->value,
'priority' => $folder->priority?->value,
'assigned_to' => $folder->assigned_to,
'notes_internal' => $folder->notes_internal,
'notes_client' => $folder->notes_client,
'created_at' => $folder->created_at?->format('Y-m-d H:i'),
],
'indexUrl' => route('folders.index'),
'updateUrl' => route('folders.update', $folder),
'folderTypeLabels' => $this->folderTypeLabels(),
'folderStatusLabels' => $this->folderStatusLabels(),
'folderPriorityLabels' => $this->folderPriorityLabels(),
'clients' => $workspace->clients()->orderBy('company_name')->get(['id', 'company_name'])->map(fn (Client $c) => [
'id' => $c->id,
'company_name' => $c->company_name,
])->values()->all(),
'workspaceUsers' => $workspace->users()
->orderBy('users.name')
->select('users.id', 'users.name', 'users.email')
->get()
->map(fn ($u) => [
'id' => $u->id,
'name' => $u->name,
'email' => $u->email,
])->values()->all(),
]);
}
/**
* Update the specified folder in storage.
*/
public function update(UpdateFolderRequest $request, Folder $folder): RedirectResponse
{
$workspace = $this->currentWorkspace($request);
$this->authorizeFolder($workspace, $folder);
$folder->update($request->validated());
return to_route('folders.index');
}
/**
* Remove the specified folder from storage.
*/
public function destroy(Request $request, Folder $folder): RedirectResponse
{
$workspace = $this->currentWorkspace($request);
$this->authorizeFolder($workspace, $folder);
$folder->delete();
return to_route('folders.index');
}
protected function authorizeFolder(Workspace $workspace, Folder $folder): void
{
if ($folder->workspace_id !== $workspace->id) {
abort(404);
}
}
}