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');