From d58f697ed8d0fdb6b7309dfcfed82685d8ee94a4 Mon Sep 17 00:00:00 2001 From: Corentin Huet Date: Fri, 14 Mar 2025 14:23:39 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20added=20the=20possibility?= =?UTF-8?q?=20of=20linking=20tickets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Bo/TicketController.php | 46 +++++++++++++++++++ app/Http/Requests/Bo/LinkTicket.php | 36 +++++++++++++++ app/Models/Ticket.php | 10 ++++ app/Policies/TicketPolicy.php | 4 +- .../2025_03_13_151503_tickets_linking.php | 35 ++++++++++++++ resources/lang/fr/crud.php | 2 + resources/ts/bo/utilities/fontawesome.ts | 4 +- .../views/bo/pages/tickets/index.blade.php | 8 ++-- .../views/bo/pages/tickets/show.blade.php | 40 ++++++++++++++++ routes/web-bo.php | 6 +++ 10 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 app/Http/Requests/Bo/LinkTicket.php create mode 100644 database/migrations/2025_03_13_151503_tickets_linking.php diff --git a/app/Http/Controllers/Bo/TicketController.php b/app/Http/Controllers/Bo/TicketController.php index 41a7069..b1fe4ff 100644 --- a/app/Http/Controllers/Bo/TicketController.php +++ b/app/Http/Controllers/Bo/TicketController.php @@ -16,6 +16,7 @@ use App\Http\Requests\Bo\StoreTicket; use App\Http\Requests\Bo\UpdateChangeOrderRequest; use App\Http\Requests\Bo\UpdateMessage; use App\Http\Requests\Bo\UpdateTicket; +use App\Http\Requests\Bo\LinkTicket; use App\Lib\Helpers\FileStorageHelper; use App\Lib\Helpers\ToolboxHelper; use App\Mail\NewTicket; @@ -139,10 +140,13 @@ class TicketController extends Controller abort(403); } + $linkedTicket = $ticket->linkedTicket()->get(); + $messages = $ticket->messages()->orderBy('created_at', 'DESC')->get(); $isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count(); return view('bo.pages.tickets.show', [ 'ticket' => $ticket, + 'linkedTicket' => $linkedTicket, 'messages' => $messages, 'isSubscribed' => $isSubscribed ]); @@ -489,6 +493,48 @@ class TicketController extends Controller return back()->with('info', trans('Le ticket n\'est plus assigné à personne')); } + + /** + * Link two tickets. + * + * @param LinkTicket $request + * @param Ticket $ticket + * + * @return \Illuminate\Http\RedirectResponse + */ + public function linkTicket(LinkTicket $request, Ticket $ticket) + { + $linkedTicket = Ticket::findOrFail($request->validated())->first(); + + if ($ticket->site_id !== $linkedTicket->site_id) { + return back()->with('error', trans('Le ticket que vous avez renseigné ne fait pas partie du même site.')); + } + + if (!$ticket->linkedTicket()->where('linked_ticket_id', $linkedTicket->id)->exists()) { + $ticket->linkedTicket()->attach($linkedTicket->id); + $linkedTicket->linkedTicket()->attach($ticket->id); + return back()->with('success', trans('Les tickets ont été liés avec succès.')); + } + + return back()->with('error', trans('Les tickets sont déjà liés ensemble.')); + } + + /** + * Unlink two tickets. + * + * @param Ticket $ticket + * @param Ticket $linkedTicket + * + * @return \Illuminate\Http\RedirectResponse + */ + public function unlinkTicket(Ticket $ticket, Ticket $linkedTicket) + { + $ticket->linkedTicket()->detach($linkedTicket->id); + $linkedTicket->linkedTicket()->detach($ticket->id); + + return back()->with('warning', trans('Le lien entre les tickets à été supprimé.')); + } + // * PRIVATES /** diff --git a/app/Http/Requests/Bo/LinkTicket.php b/app/Http/Requests/Bo/LinkTicket.php new file mode 100644 index 0000000..3ce7d4e --- /dev/null +++ b/app/Http/Requests/Bo/LinkTicket.php @@ -0,0 +1,36 @@ +check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + $ticket = $this->route('ticket'); + return [ + 'linked_ticket_id' => [ + 'required', + 'numeric', + 'exists:tickets,id', + 'not_in:' . $ticket->id, + ] + ]; + } +} diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 1fa1732..c56977d 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -193,6 +193,16 @@ class Ticket extends Model return $this->belongsTo(TicketType::class, 'type', 'type'); } + /** + * BelongsToMany Linked Ticket + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function linkedTicket() + { + return $this->belongsToMany(Ticket::class, 'linked_tickets', 'ticket_id', 'linked_ticket_id'); + } + // * METHODS /** diff --git a/app/Policies/TicketPolicy.php b/app/Policies/TicketPolicy.php index bcb6401..14ede6f 100644 --- a/app/Policies/TicketPolicy.php +++ b/app/Policies/TicketPolicy.php @@ -68,12 +68,12 @@ class TicketPolicy } /** - * Determine if the user can change the order of a given ticket. + * Determine if the user can link ticket . * * @param \App\Models\User $user * @return boolean */ - public function changeOrder(User $user) + public function linkTicket(User $user) { return $user->isAdmin(); } diff --git a/database/migrations/2025_03_13_151503_tickets_linking.php b/database/migrations/2025_03_13_151503_tickets_linking.php new file mode 100644 index 0000000..801bbb3 --- /dev/null +++ b/database/migrations/2025_03_13_151503_tickets_linking.php @@ -0,0 +1,35 @@ +unsignedBigInteger('ticket_id') + ->constrained()->cascadeOnDelete(); + $table->unsignedBigInteger('linked_ticket_id') + ->constrained()->cascadeOnDelete(); + $table->foreign('ticket_id')->references('id')->on('tickets'); + $table->foreign('linked_ticket_id')->references('id')->on('tickets'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('linked_tickets'); + } +}; diff --git a/resources/lang/fr/crud.php b/resources/lang/fr/crud.php index ddb1331..d219d12 100644 --- a/resources/lang/fr/crud.php +++ b/resources/lang/fr/crud.php @@ -89,5 +89,7 @@ return [ 'sort_delete' => 'Supprimer le tri', 'sort_arrow' => 'Ici, les flèches sont utilisées pour trier et non pour filtrer', 'right' => 'Vous n\'avez pas les droits', + 'view_linked_ticket' => 'voir le ticket :model', + 'unlink_ticket' => 'supprimer le lien avec le ticket :model' ] ]; diff --git a/resources/ts/bo/utilities/fontawesome.ts b/resources/ts/bo/utilities/fontawesome.ts index fa4d683..f3568c8 100644 --- a/resources/ts/bo/utilities/fontawesome.ts +++ b/resources/ts/bo/utilities/fontawesome.ts @@ -65,7 +65,8 @@ import { faUndoAlt, faUsers, faWandMagic, - faXmark + faXmark, + faLinkSlash } from "@fortawesome/free-solid-svg-icons"; config.autoReplaceSvg = false; @@ -126,6 +127,7 @@ library.add(faCircleExclamation); library.add(faCircleInfo); library.add(faPaintRoller); library.add(faTicket); +library.add(faLinkSlash); // Front library.add(faChevronLeft); diff --git a/resources/views/bo/pages/tickets/index.blade.php b/resources/views/bo/pages/tickets/index.blade.php index eb9299e..d0a4b94 100644 --- a/resources/views/bo/pages/tickets/index.blade.php +++ b/resources/views/bo/pages/tickets/index.blade.php @@ -199,10 +199,10 @@ $cols = array_merge($cols, [ 'tickets' => Str::ucfirst(__('validation.custom.tickets')), 'assigned_to' => Str::ucfirst(__('validation.custom.assigned_to')), - 'date_mep' => Str::ucfirst(__('validation.custom.date_mep')), ]); } $cols = array_merge($cols, [ + 'date_mep' => Str::ucfirst(__('validation.custom.date_mep')), 'created_at' => Str::ucfirst(__('validation.attributes.created_at')), ]); @endphp @@ -278,10 +278,10 @@ {{-- assigned_to / date_mep --}} @if (Auth::user()->isAdmin()) {{ $ticket->assignedUser ? $ticket->assignedUser->name : __('Personne') }} - - {{ is_null($ticket->date_mep) ? Str::ucfirst(__("validation.custom.date_mep_empty")) : $ticket->date_mep->format('d/m/Y') }} - @endif + + {{ is_null($ticket->date_mep) ? Str::ucfirst(__("validation.custom.date_mep_empty")) : $ticket->date_mep->format('d/m/Y') }} + {{ Carbon\Carbon::parse($ticket->created_at)->format('d/m/Y à H:i') }}
diff --git a/resources/views/bo/pages/tickets/show.blade.php b/resources/views/bo/pages/tickets/show.blade.php index 4d590bb..1b6f759 100644 --- a/resources/views/bo/pages/tickets/show.blade.php +++ b/resources/views/bo/pages/tickets/show.blade.php @@ -273,6 +273,46 @@
@endif
+
+
+

{{ __('Tickets liés') }}

+
+ @can('linkTicket', $ticket) +
+ +
+ @endcan +
+ @foreach ($linkedTicket as $linked) +
+ @can('linkTicket', $ticket) + + @endcan +
+ @endforeach +
+
+
@can('createGitlabIssue', $ticket)
@csrf diff --git a/routes/web-bo.php b/routes/web-bo.php index 8de8ae9..5dd91c5 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -154,6 +154,12 @@ Route::middleware('admin-secure') Route::post('/{ticket}/subscribe', [TicketController::class, 'subscribe']) ->middleware('can:suscribeToTicket,ticket') ->name('subscribe'); + Route::post('/{ticket}/link', [TicketController::class, 'linkTicket']) + ->middleware('can:linkTicket,ticket') + ->name('link'); + Route::delete('/{ticket}/unlink/{linkedTicket}', [TicketController::class, 'unlinkTicket']) + ->middleware('can:linkTicket,ticket') + ->name('unlink'); Route::put('/{ticket}/archive', [TicketController::class, 'setToArchive']) ->middleware('can:changeStatus,ticket') ->name('archive');