Tickets Assigné

This commit is contained in:
Jérémy Munsch 2022-05-24 17:23:36 +02:00
parent d6b6d5a6fb
commit 75da3f91b3
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
11 changed files with 318 additions and 34 deletions

View file

@ -6,6 +6,7 @@ use App\Enums\TicketStatus;
use App\Enums\TicketType;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Requests\AssignTicketToUserRequest;
use App\Http\Requests\ChangeTicketStatus;
use App\Http\Requests\CreateMessage;
use App\Http\Requests\SortTickets;
@ -20,7 +21,6 @@ use App\Models\Ticket;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
@ -47,7 +47,7 @@ class TicketController extends Controller
{
// * Handle Ajax
if ($request->ajax()) {
$tickets = Ticket::with('user');
$tickets = Ticket::with(['user', 'assignedUser']);
if ($request->s) {
$tickets = $tickets->where('text', 'LIKE', "%$request->s%")
->orWhere('id', $request->s)
@ -372,7 +372,7 @@ class TicketController extends Controller
{
/** @var \App\Models\User */
$user = auth()->user();
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
$user->subscriptions()->toggle($ticket->id);
if ($user->subscriptions()->where('ticket_id', $ticket->id)->exists()) {
return back()
->with('success', trans('Vous avez été inscrit'));
@ -388,7 +388,7 @@ class TicketController extends Controller
*/
public function reopen(Ticket $ticket)
{
$message = trans(":ticketName à réouvert le ticket n°:ticketNumber", [
$message = trans(':ticketName à réouvert le ticket n°:ticketNumber', [
'ticketName' => $ticket->user->name,
'ticketNumber' => $ticket->id,
]);
@ -397,10 +397,64 @@ class TicketController extends Controller
$ticket->saveQuietly();
event(new TicketStatusChanged($ticket, $oldStatus, null, $message));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
return back()->with('warning', trans('Votre ticket à été réouvert.'));
}
/**
* Assign ticket to user ticket
*
* @param AssignTicketToUserRequest $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function assignTo(AssignTicketToUserRequest $request, Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
/** @var \App\Models\User|null */
if ($assignedUser = User::whereId($request->assign)->first()) {
$message = trans(':admin à assigné le ticket n°:ticketNumber à :assignedAdmin', [
'admin' => $user->name,
'ticketNumber' => $ticket->id,
'assignedAdmin' => $assignedUser->name,
]);
$ticket->assignedUser()->associate($assignedUser);
} else {
$message = trans(
':admin modifié l\'assignation du ticket n°:ticketNumber, il n\'est plus assigné à quelqu\'un.',
[
'admin' => $user->name,
'ticketNumber' => $ticket->id,
]
);
$ticket->assignedUser()->dissociate();
}
$ticket->saveQuietly();
/** @var \App\Enums\TicketStatus */
$oldStatus = $ticket->status;
event(new TicketStatusChanged($ticket, $oldStatus, null, $message));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
if ($assignedUser) {
return back()->with('info', trans('Le ticket à été assigné à :assignedUser', [
'assignedUser' => $assignedUser->name
]));
}
return back()->with('info', trans('Le ticket n\'est plus assigné à personne'));
}
// * PRIVATES
/**
@ -413,11 +467,15 @@ class TicketController extends Controller
private function filterTickets(Builder $tickets, Request $request): Builder
{
if ($request->rst) {
Session::remove('tickets.ass');
Session::remove('tickets.st');
Session::remove('tickets.pr');
Session::remove('tickets.sort_col');
Session::remove('tickets.sort_way');
}
if ($request->assr) {
Session::remove('tickets.ass');
}
if ($request->prs) {
Session::remove('tickets.pr');
}
@ -436,14 +494,23 @@ class TicketController extends Controller
if ($request->st) {
Session::put('tickets.st', $request->st);
}
if ($request->ass) {
Session::put('tickets.ass', $request->ass);
}
Session::save();
if (Session::has('tickets.pr')) {
$tickets = $tickets->where('site_id', Session::get('tickets.pr'));
$tickets->where('site_id', Session::get('tickets.pr'));
}
if (Session::has('tickets.ass')) {
$tickets->whereHas('assignedUser', function ($q) {
$q->whereId(Session::get('tickets.ass'));
});
}
if (Session::has('tickets.st')) {
$tickets = $tickets->where('status', Session::get('tickets.st'));
$tickets->where('status', Session::get('tickets.st'));
}
if (
@ -451,15 +518,15 @@ class TicketController extends Controller
Session::has('tickets.sort_way') and
in_array(Session::get('tickets.sort_col'), [
'id', 'site_id', 'created_at', 'created_by',
'type', 'status'
'type', 'status', 'assigned_to'
])
) {
$tickets = $tickets->orderBy(
$tickets->orderBy(
Session::get('tickets.sort_col'),
Session::get('tickets.sort_way')
);
} else {
$tickets = $tickets->orderBy('id', 'DESC');
$tickets->orderBy('id', 'DESC');
}
return $tickets;
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
class AssignTicketToUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
/** @var \App\Models\User */
$user = auth()->user();
return auth()->check() and $user->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'assign' => [
'nullable', 'exists:users,id',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
/** @var \App\Models\User $user */
if ($user = User::whereId($value)->first() and !$user->isAdmin()) {
$fail(trans('Cet utilisateur ne peut se voir assigner un ticket'));
}
}
]
];
}
}

View file

@ -110,6 +110,17 @@ class Ticket extends Model
return $this->belongsTo(User::class, 'created_by');
}
/**
* BelongsTo User relation
* The user that is in charge of the ticket
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function assignedUser()
{
return $this->belongsTo(User::class, 'assigned_to');
}
/**
* HasMany Message relation
*

View file

@ -5,10 +5,12 @@ namespace App\Models;
use App\Enums\Role;
use App\Jobs\SendUserCreatedEmail;
use App\Notifications\ResetPassword;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
@ -24,17 +26,29 @@ class User extends Authenticatable
use Notifiable;
use SoftDeletes;
/**
* The attributes that should be cast to Enums.
*
* @var array
*/
protected $enumCasts = [
'role' => Role::class
];
/**
* Crud Label
*
* @var string
*/
protected $crudLabelColumn = 'name';
/**
* Crud Value
*
* @var string
*/
protected $crudValueColumn = 'id';
// stores unhashed pwd
public $tmpPass = null;
/**
* The attributes that are mass assignable.
*
@ -67,7 +81,12 @@ class User extends Authenticatable
'email_verified_at' => 'datetime',
];
public function __construct($attributes = [])
/**
* User
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'name' => [
@ -148,6 +167,11 @@ class User extends Authenticatable
parent::__construct($attributes);
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (User $user) {
@ -158,46 +182,89 @@ class User extends Authenticatable
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
}
Cache::flush('AdminUsers');
});
static::updating(function (User $user) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
}
Cache::flush('AdminUsers');
});
}
// * RELATIONSHIPS
/**
* HasMany Ticket relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tickets()
{
return $this->hasMany(Ticket::class, 'created_by');
}
/**
* HasMany Message relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function messages()
{
return $this->hasMany(Messages::class, 'user_id');
return $this->hasMany(Message::class, 'user_id');
}
/**
* BelongsToMany Ticket relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function subscriptions()
{
return $this->belongsToMany(Ticket::class, 'subscriptions');
}
/**
* BelongsToMany Site relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function sites()
{
return $this->belongsToMany(Site::class);
}
/**
* HasMany Schedule relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function schedules()
{
return $this->hasMany(Schedule::class);
}
// * METHODS
/**
* Send User creation confirm mail
*
* @return void
*/
public function sendMailConfirm()
{
dispatch(new SendUserCreatedEmail($this));
}
/**
* Send User reset password mail
*
* @param mixed $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
/** @var string $token */
$this->notify(new ResetPassword($token));
}
@ -211,4 +278,16 @@ class User extends Authenticatable
return ($this->role->equals(Role::developper()) or
$this->role->equals(Role::admin()));
}
/**
* Get All Admin users
*
* @return Collection
*/
public static function adminUsers(): Collection
{
return Cache::rememberForever('AdminUsers', function () {
return self::whereIn('role', [Role::admin(), Role::developper()])->get();
});
}
}

View file

@ -13,7 +13,7 @@ class ResetPassword extends ResetPasswordNotification
/**
* Get the reset password notification mail message for the given URL.
*
* @param string $url
* @param mixed $url
* @return \Illuminate\Notifications\Messages\MailMessage
*/
protected function buildMailMessage($url)
@ -22,6 +22,7 @@ class ResetPassword extends ResetPasswordNotification
->subject(trans('Reinitialisation de mot de passe'))
//phpcs:ignore Generic.Files.LineLength.TooLong
->line(trans('Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.'))
/** @var string $url */
->action(trans('Réinitialiser le mot de passe'), $url)
//phpcs:ignore Generic.Files.LineLength.TooLong
->line(trans('Ce lien de réinitialisation de mot de passe expirera dans :count minutes. ', ['count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]))

View file

@ -12,7 +12,24 @@ use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
*/
abstract class TicketStaticRules extends StaticRules
{
protected static function ticketIsNotArchived(Ticket $ticket)
/**
* Ticket is not in production
*
* @param Ticket $ticket
* @return boolean
*/
protected static function ticketIsNotInProduction(Ticket $ticket): bool
{
return !$ticket->status->equals(TicketStatus::production());
}
/**
* Ticket is not archived
*
* @param Ticket $ticket
* @return boolean
*/
protected static function ticketIsNotArchived(Ticket $ticket): bool
{
return !$ticket->status->equals(TicketStatus::archived());
}

View file

@ -18,11 +18,10 @@ class TicketPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param User $user
* @param Model $model
* @return void
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
public function setPolicies(User $user, Model $model = null)
{
/**
@ -51,7 +50,12 @@ class TicketPolicy extends RootPolicy
URR::make('userIsAdmin', $user),
TicketStaticRules::make('ticketIsNotArchived', $model),
]),
'reopen' => URR::make('userIsClient', $user)
'reopen' => URR::make('userIsClient', $user),
'assignTo' => StaticRules::and([
URR::make('userIsAdmin', $user),
TicketStaticRules::make('ticketIsNotInProduction', $model),
TicketStaticRules::make('ticketIsNotArchived', $model)
])
]
];
}

View file

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AssignUserToTickets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('tickets', function (Blueprint $table) {
$table->foreignId('assigned_to')
->comment('La personne a qui est assigné le ticket.')
->nullable()
->after('created_by')
->constrained('users')
->nullOnDelete();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropForeign(['assigned_to']);
$table->dropColumn('assigned_to');
});
}
}

View file

@ -42,6 +42,16 @@ $userPr = Auth::user()->sites ?? collect([]);
</div>
</div>
<div class="row">
<div class="col">
<div class="float-end mt-3 me-2">
<select class="form-select" onchange="window.location='{{ route('ticket.index') }}?'+(this.value == -1 ? 'assr=-1' : 'ass='+this.value);">
<option value="-1">{{ __('Tous') }}</option>
@foreach(\App\Models\User::adminUsers() as $user)
<option value="{{ $user->id }}" @if(intval(session('tickets.ass'))==$user->id)selected="selected" @endif>{{ $user->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col">
<form id="searchTicketForm" class="float-end mt-3 w-75" action="{{ request()->fullUrlWithQuery(['search' => '']) }}">
<div class="input-group mb-3">
@ -61,20 +71,21 @@ $userPr = Auth::user()->sites ?? collect([]);
#&nbsp;
@include('modules.sorter', ['colname' => 'id'])
</th>
<th scope="col">
{{__('Projet') }}&nbsp;
@include('modules.sorter', ['colname' => 'site_id'])
</th>
<th scope="col">
{{__('Type') }}&nbsp;
@include('modules.sorter', ['colname' => 'type'])
</th>
<th scope="col">{{__('Fichier') }}</th>
<th scope="col">{{__('Fichier 2') }}</th>
<th scope="col">{{__('Fichiers') }}</th>
<th scope="col">
{{__('Date de création') }}&nbsp;
{{__('Crée le') }}&nbsp;
@include('modules.sorter', ['colname' => 'created_at'])
</th>
@if(Auth::user()->isAdmin())
<th scope="col">
{{__('Assigné à') }}&nbsp;
@include('modules.sorter', ['colname' => 'assigned_to'])
</th>
@endif
<th scope="col">
{{__('Créé par') }}&nbsp;
@include('modules.sorter', ['colname' => 'created_by'])
@ -119,12 +130,11 @@ $userPr = Auth::user()->sites ?? collect([]);
@endphp
<tr>
<td scope="row">
{{ $ticket->id }}
@if ($ticket->is_internal)
🔒
@endif
<div class="d-flex justify-content-between">
<span class="me-1">{{ $ticket->id }}&nbsp;@if($ticket->is_internal)🔒@endif</span>
<span class="text-end">{{ $ticket->site->name }}</span>
</div>
</td>
<td>{{ $ticket->site->name }}</td>
<td>
<span class="badge {{ $ticketClass }}">{{ $ticket->type->label }}</span>
</td>
@ -132,13 +142,14 @@ $userPr = Auth::user()->sites ?? collect([]);
@if ($ticket->file !== null)
<a data-fslightbox="gallery" href="{{ asset($ticket->file) }}" class="btn btn-sm btn-outline-primary" target="_blank">Fichier</a>
@endif
</td>
<td>
@if ($ticket->file2 !== null)
<a data-fslightbox="gallery" href="{{ asset($ticket->file2) }}" class="btn btn-sm btn-outline-primary" target="_blank">Fichier 2</a>
@endif
</td>
<td>{{ Carbon\Carbon::parse($ticket->created_at)->format('d/m/Y à H:i') }}</td>
@if(Auth::user()->isAdmin())
<td>{{ $ticket->assignedUser ? $ticket->assignedUser->name : __('Personne') }}</td>
@endif
<td>{{ $ticket->user ? $ticket->user->name : __('Utilisateur supprimé') }}</td>
<td><span class="rounded {{ $cls }}">{{ $ticket->status->label }}</span></td>
<td>

View file

@ -22,6 +22,17 @@
</div>
</form>
@endcan
@can('assignTo', $ticket)
<form action="{{ route('ticket.assignTo', ['ticket' => $ticket]) }}" method="POST" id="form_assign" class="col me-2">
@csrf
<select class="form-select" name="assign" id="assign" onchange="document.getElementById('form_assign').submit();">
<option value="" @if(!$ticket->assignedUser)selected @endif>{{ __('Personne n\'est assigné') }}</option>
@foreach(\App\Models\User::adminUsers() as $user)
<option value="{{ $user->id }}" @if(optional($ticket->assignedUser)->id == $user->id)selected @endif>{{ $user->name }}</option>
@endforeach
</select>
</form>
@endcan
@can('changeStatus', $ticket)
<form action="{{ route('ticket.changeStatus', $ticket->id) }}" method="POST" id="form_status" class="col">
@csrf

View file

@ -86,6 +86,9 @@ Route::middleware(['auth'])->group(function () {
// * Ticket
Route::prefix('/ticket')->name('ticket.')->group(function () {
Route::post('/{ticket}/assignTo', [TicketController::class, 'assignTo'])
->middleware('can:assignTo,ticket')
->name('assignTo');
Route::post('/{ticket}/reopen', [TicketController::class, 'reopen'])
->middleware('can:reopen,ticket')
->name('reopen');